mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2025-12-05 20:39:59 -08:00
[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:
@@ -153,8 +153,10 @@ set(cockatrice_SOURCES
|
||||
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
|
||||
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
||||
src/interface/widgets/general/background_sources.cpp
|
||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||
src/interface/widgets/general/display/banner_widget.cpp
|
||||
src/interface/widgets/general/display/bar_widget.cpp
|
||||
src/interface/widgets/general/display/color_bar.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_label.cpp
|
||||
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||
src/interface/widgets/general/display/labeled_input.cpp
|
||||
@@ -202,6 +204,7 @@ set(cockatrice_SOURCES
|
||||
src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
|
||||
src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp
|
||||
src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
|
||||
src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp
|
||||
src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
|
||||
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
|
||||
@@ -220,6 +223,19 @@ set(cockatrice_SOURCES
|
||||
src/interface/window_main.cpp
|
||||
src/main.cpp
|
||||
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
|
||||
|
||||
@@ -32,7 +32,10 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent,
|
||||
CardGroupDisplayWidget::updateCardDisplays();
|
||||
|
||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition);
|
||||
connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &CardGroupDisplayWidget::onSelectionChanged);
|
||||
if (selectionModel) {
|
||||
connect(selectionModel, &QItemSelectionModel::selectionChanged, this,
|
||||
&CardGroupDisplayWidget::onSelectionChanged);
|
||||
}
|
||||
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval);
|
||||
}
|
||||
|
||||
@@ -179,8 +182,10 @@ void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSort
|
||||
void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mousePressEvent(event);
|
||||
if (selectionModel) {
|
||||
selectionModel->clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
|
||||
{
|
||||
|
||||
@@ -38,8 +38,10 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
|
||||
displayCards();
|
||||
|
||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition);
|
||||
if (selectionModel) {
|
||||
connect(selectionModel, &QItemSelectionModel::selectionChanged, this,
|
||||
&DeckCardZoneDisplayWidget::onSelectionChanged);
|
||||
}
|
||||
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "background_plate_widget.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QColor>
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
|
||||
BackgroundPlateWidget::BackgroundPlateWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
setAutoFillBackground(true); // For automatic background filling
|
||||
}
|
||||
|
||||
void BackgroundPlateWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// Set the background color to semi-transparent black with rounded corners
|
||||
QRect rect = this->rect();
|
||||
painter.setPen(Qt::NoPen); // No border
|
||||
if (focused) {
|
||||
painter.setBrush(QColor(85, 190, 75, 140));
|
||||
} else {
|
||||
painter.setBrush(QColor(0, 0, 0, 140)); // semi-transparent black
|
||||
}
|
||||
painter.drawRoundedRect(rect, 6, 6); // rounded corners
|
||||
}
|
||||
|
||||
void BackgroundPlateWidget::setFocused(bool _focused)
|
||||
{
|
||||
focused = _focused;
|
||||
update();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef COCKATRICE_BACKGROUND_PLATE_WIDGET_H
|
||||
#define COCKATRICE_BACKGROUND_PLATE_WIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class BackgroundPlateWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BackgroundPlateWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setFocused(bool focused);
|
||||
|
||||
private:
|
||||
bool focused = false;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_BACKGROUND_PLATE_WIDGET_H
|
||||
163
cockatrice/src/interface/widgets/general/display/color_bar.cpp
Normal file
163
cockatrice/src/interface/widgets/general/display/color_bar.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
|
||||
#include "color_bar.h"
|
||||
|
||||
#include <QLinearGradient>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QToolTip>
|
||||
|
||||
ColorBar::ColorBar(const QMap<QString, int> &_colors, QWidget *parent) : QWidget(parent), colors(_colors)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void ColorBar::setColors(const QMap<QString, int> &_colors)
|
||||
{
|
||||
colors = _colors;
|
||||
update();
|
||||
}
|
||||
|
||||
QSize ColorBar::minimumSizeHint() const
|
||||
{
|
||||
return QSize(200, 22);
|
||||
}
|
||||
|
||||
void ColorBar::paintEvent(QPaintEvent *)
|
||||
{
|
||||
if (colors.isEmpty())
|
||||
return;
|
||||
|
||||
int total = 0;
|
||||
for (int v : colors.values())
|
||||
total += v;
|
||||
|
||||
// Prevent divide-by-zero
|
||||
if (total == 0)
|
||||
return;
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
const int w = width();
|
||||
const int h = height();
|
||||
int x = 0;
|
||||
|
||||
// Draw rounded border background
|
||||
QRectF bounds(0.5, 0.5, w - 1, h - 1);
|
||||
p.setPen(QPen(Qt::black, 1));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawRoundedRect(bounds, 6, 6);
|
||||
|
||||
// Clip to inside the border
|
||||
p.setClipRect(bounds.adjusted(2, 2, -2, -2));
|
||||
|
||||
// Ensure predictable order
|
||||
QList<QString> sortedKeys = colors.keys();
|
||||
std::sort(sortedKeys.begin(), sortedKeys.end()); // Sort alphabetically
|
||||
|
||||
// Draw each color segment in the sorted order
|
||||
for (const QString &key : sortedKeys) {
|
||||
int value = colors[key];
|
||||
double ratio = double(value) / total;
|
||||
|
||||
if (ratio <= minRatioThreshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int segmentWidth = int(ratio * w);
|
||||
|
||||
// Ensure the segment width is at least 1 to avoid degenerate rectangles
|
||||
if (segmentWidth < 1)
|
||||
segmentWidth = 1;
|
||||
|
||||
QColor base = colorFromName(key);
|
||||
|
||||
// Slight gradient for nicer look
|
||||
QLinearGradient grad(x, 0, x, h);
|
||||
grad.setColorAt(0, base.lighter(120));
|
||||
grad.setColorAt(1, base.darker(120));
|
||||
|
||||
p.fillRect(QRect(x, 0, segmentWidth, h), grad);
|
||||
|
||||
x += segmentWidth;
|
||||
}
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void ColorBar::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
isHovered = true;
|
||||
}
|
||||
#else
|
||||
void ColorBar::enterEvent(QEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
isHovered = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ColorBar::leaveEvent(QEvent *)
|
||||
{
|
||||
isHovered = false;
|
||||
}
|
||||
|
||||
void ColorBar::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!isHovered || colors.isEmpty())
|
||||
return;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
int x = int(event->position().x());
|
||||
QPoint gp = event->globalPosition().toPoint();
|
||||
#else
|
||||
int x = event->pos().x();
|
||||
QPoint gp = event->globalPos();
|
||||
#endif
|
||||
|
||||
QString text = tooltipForPosition(x);
|
||||
if (!text.isEmpty())
|
||||
QToolTip::showText(gp, text, this);
|
||||
}
|
||||
|
||||
QString ColorBar::tooltipForPosition(int x) const
|
||||
{
|
||||
int total = 0;
|
||||
for (int v : colors.values())
|
||||
total += v;
|
||||
|
||||
if (total == 0)
|
||||
return {};
|
||||
|
||||
int pos = 0;
|
||||
for (auto it = colors.cbegin(); it != colors.cend(); ++it) {
|
||||
const double ratio = double(it.value()) / total;
|
||||
const int segmentWidth = int(ratio * width());
|
||||
|
||||
if (x >= pos && x < pos + segmentWidth) {
|
||||
const double percent = (100.0 * it.value()) / total;
|
||||
return QString("%1: %2 cards (%3%)").arg(it.key()).arg(it.value()).arg(QString::number(percent, 'f', 1));
|
||||
}
|
||||
|
||||
pos += segmentWidth;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QColor ColorBar::colorFromName(const QString &name) const
|
||||
{
|
||||
static QMap<QString, QColor> map = {
|
||||
{"R", QColor(220, 30, 30)}, {"G", QColor(40, 170, 40)}, {"U", QColor(40, 90, 200)},
|
||||
{"W", QColor(235, 235, 230)}, {"B", QColor(30, 30, 30)},
|
||||
};
|
||||
|
||||
if (map.contains(name))
|
||||
return map[name];
|
||||
|
||||
QColor c(name);
|
||||
if (!c.isValid())
|
||||
c = Qt::gray;
|
||||
|
||||
return c;
|
||||
}
|
||||
128
cockatrice/src/interface/widgets/general/display/color_bar.h
Normal file
128
cockatrice/src/interface/widgets/general/display/color_bar.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef COCKATRICE_COLOR_BAR_H
|
||||
#define COCKATRICE_COLOR_BAR_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* @class ColorBar
|
||||
* @brief A widget for visualizing proportional color distributions as a horizontal bar.
|
||||
*
|
||||
* This widget renders a horizontal bar divided into colored segments whose widths reflect
|
||||
* the relative values associated with each color key in a `QMap<QString, int>`. The class
|
||||
* is designed as a small, lightweight, and self-contained visualization component suitable
|
||||
* for representing distributions such as color counts, mana statistics, categorical frequencies, and similar data sets.
|
||||
*
|
||||
* Key features:
|
||||
* - Filled segments for better visual clarity.
|
||||
* - Deterministic alphabetical ordering of color keys.
|
||||
* - Optional minimum percentage threshold for filtering out insignificant segments.
|
||||
* - Mouse-hover tooltips showing each segment’s key, count, and percentage of total.
|
||||
*
|
||||
* Default color mappings exist for `"R"`, `"G"`, `"U"`, `"W"`, and `"B"`, using named
|
||||
* colors, but any string recognized by `QColor` may be used. If an unknown name is provided,
|
||||
* the segment will fall back to gray.
|
||||
*
|
||||
* This component is display-only and does not interpret or mutate domain-level data.
|
||||
*/
|
||||
class ColorBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a ColorBar widget.
|
||||
*
|
||||
* @param colors Map of color identifiers to integer counts.
|
||||
* @param parent Optional parent widget.
|
||||
*/
|
||||
explicit ColorBar(const QMap<QString, int> &colors, QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Updates the color distribution map.
|
||||
* @param colors New color → count mapping.
|
||||
*
|
||||
* Triggers an immediate repaint.
|
||||
*/
|
||||
void setColors(const QMap<QString, int> &colors);
|
||||
|
||||
/**
|
||||
* @brief Sets a minimum percentage threshold below which segments are not drawn.
|
||||
*
|
||||
* @param treshold Percentage from 0 to 100.
|
||||
*
|
||||
* Internally converted into a ratio (0.05 = 5%).
|
||||
*/
|
||||
void setMinPercentThreshold(double treshold)
|
||||
{
|
||||
minRatioThreshold = treshold / 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the recommended minimum size.
|
||||
*/
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Paints the color distribution bar.
|
||||
*
|
||||
* Draws:
|
||||
* - A rounded border
|
||||
* - Filled segments for each color
|
||||
* - Only segments above the minimum ratio threshold
|
||||
*/
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
/**
|
||||
* @brief Handles mouse hover entering (Qt6 version).
|
||||
*/
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
/**
|
||||
* @brief Handles mouse hover entering (Qt5 version).
|
||||
*/
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Handles mouse hover leaving.
|
||||
*/
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
/**
|
||||
* @brief Handles mouse movement to update contextual tooltips.
|
||||
*/
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
/// Map of color keys to counts used for rendering.
|
||||
QMap<QString, int> colors;
|
||||
|
||||
/// True if the mouse is currently inside the widget.
|
||||
bool isHovered = false;
|
||||
|
||||
/// Minimum ratio a segment must exceed to be drawn.
|
||||
double minRatioThreshold = 0.0;
|
||||
|
||||
/**
|
||||
* @brief Converts a color name into a display QColor.
|
||||
*
|
||||
* Recognized special keys: `"R", "G", "U", "W", "B"`.
|
||||
* Other strings are treated as QColor names or fall back to gray.
|
||||
*/
|
||||
QColor colorFromName(const QString &name) const;
|
||||
|
||||
/**
|
||||
* @brief Returns tooltip text for a given x-coordinate in the bar.
|
||||
*
|
||||
* @param x Horizontal coordinate relative to widget.
|
||||
* @return Tooltip text or empty string if no segment applies.
|
||||
*/
|
||||
QString tooltipForPosition(int x) const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COLOR_BAR_H
|
||||
@@ -13,11 +13,17 @@
|
||||
ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_TranslucentBackground); // Allows transparency.
|
||||
setText("<font color='white'>" + text + "</font>"); ///< Ensures the text is rendered in white.
|
||||
setLabelText(text);
|
||||
setAlignment(Qt::AlignCenter); ///< Centers the text within the label.
|
||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ///< Ensures minimum size constraints.
|
||||
}
|
||||
|
||||
void ShadowBackgroundLabel::setLabelText(const QString &text)
|
||||
{
|
||||
setText("<font color='white'>" + text + "</font>"); ///< Ensures the text is rendered in white.
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles resizing of the label.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,7 @@ class ShadowBackgroundLabel : public QLabel
|
||||
|
||||
public:
|
||||
explicit ShadowBackgroundLabel(QWidget *parent, const QString &text);
|
||||
void setLabelText(const QString &text);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
@@ -196,6 +196,9 @@ QGroupBox *HomeWidget::createButtons()
|
||||
auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
||||
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
||||
boxLayout->addWidget(edhrecButton);
|
||||
auto archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors);
|
||||
connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab);
|
||||
boxLayout->addWidget(archidektButton);
|
||||
auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
|
||||
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
||||
boxLayout->addWidget(replaybutton);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "archidekt_deck_listing_api_response.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
|
||||
void ArchidektDeckListingApiResponse::fromJson(const QJsonObject &json)
|
||||
{
|
||||
count = json.value("count").toInt();
|
||||
next = QUrl(json.value("next").toString());
|
||||
|
||||
QJsonArray containerJson = json.value("results").toArray();
|
||||
|
||||
for (const QJsonValue &deckListingValue : containerJson) {
|
||||
ArchidektApiResponseDeckListingContainer listingResult;
|
||||
listingResult.fromJson(deckListingValue.toObject());
|
||||
results.append(listingResult);
|
||||
}
|
||||
}
|
||||
|
||||
void ArchidektDeckListingApiResponse::debugPrint() const
|
||||
{
|
||||
qDebug() << "Count:" << count;
|
||||
qDebug() << "Next:" << next;
|
||||
|
||||
qDebug() << "Results:";
|
||||
for (const auto &deckListing : results) {
|
||||
deckListing.debugPrint();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_H
|
||||
#define COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_H
|
||||
|
||||
#include "deck_listings/archidekt_api_response_deck_listing_container.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
class ArchidektDeckListingApiResponse
|
||||
{
|
||||
|
||||
public:
|
||||
int count;
|
||||
QUrl next;
|
||||
QVector<ArchidektApiResponseDeckListingContainer> results;
|
||||
|
||||
void fromJson(const QJsonObject &json);
|
||||
void debugPrint() const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_LISTING_API_RESPONSE_H
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "archidekt_api_response_card.h"
|
||||
|
||||
void ArchidektApiResponseCard::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toInt();
|
||||
artist = json.value("artist").toString();
|
||||
tcgProductId = json.value("tcgProductId").toInt();
|
||||
ckFoilId = json.value("ckFoilId").toInt();
|
||||
ckNormalId = json.value("ckNormalId").toInt();
|
||||
cmEd = json.value("cmEd").toString();
|
||||
scgSku = json.value("scgSku").toString();
|
||||
scgFoilSku = json.value("scgFoilSku").toString();
|
||||
collectorNumber = json.value("collectorNumber").toString();
|
||||
multiverseId = json.value("multiverseId").toInt();
|
||||
mtgoFoilId = json.value("mtgoFoilId").toInt();
|
||||
mtgoNormalId = json.value("mtgoNormalId").toInt();
|
||||
uid = json.value("uid").toString();
|
||||
displayName = json.value("displayName").toString();
|
||||
releasedAt = json.value("releasedAt").toString();
|
||||
|
||||
edition.fromJson(json.value("edition").toObject());
|
||||
|
||||
flavor = json.value("flavor").toString();
|
||||
// TODO but not really important
|
||||
// games = {""};
|
||||
// options = {""};
|
||||
scryfallImageHash = json.value("scryfallImageHash").toString();
|
||||
oracleCard = json.value("oracleCard").toObject();
|
||||
owned = json.value("owned").toInt();
|
||||
pinnedStatus = json.value("pinnedStatus").toInt();
|
||||
rarity = json.value("rarity").toString();
|
||||
// TODO but not really important
|
||||
// globalCategories = {""};
|
||||
}
|
||||
|
||||
void ArchidektApiResponseCard::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "id:" << artist;
|
||||
qDebug() << "artist:" << tcgProductId;
|
||||
qDebug() << "tcgProductId:" << ckFoilId;
|
||||
qDebug() << "ckFoilId:" << ckNormalId;
|
||||
qDebug() << "ckNormalId:" << cmEd;
|
||||
qDebug() << "cmEd:" << scgSku;
|
||||
qDebug() << "scgSku:" << scgFoilSku;
|
||||
qDebug() << "scgFoilSku:" << collectorNumber;
|
||||
qDebug() << "collectorNumber:" << multiverseId;
|
||||
qDebug() << "multiverseId:" << mtgoFoilId;
|
||||
qDebug() << "mtgoFoilId:" << mtgoNormalId;
|
||||
qDebug() << "mtgoNormalId:" << uid;
|
||||
qDebug() << "uid:" << displayName;
|
||||
qDebug() << "displayName:" << releasedAt;
|
||||
qDebug() << "releasedAt:" << flavor;
|
||||
qDebug() << "flavor:" << games;
|
||||
qDebug() << "games:" << options;
|
||||
qDebug() << "options:" << scryfallImageHash;
|
||||
qDebug() << "scryfallImageHash:" << owned;
|
||||
qDebug() << "owned:" << pinnedStatus;
|
||||
qDebug() << "pinnedStatus:" << rarity;
|
||||
qDebug() << "rarity:" << globalCategories;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H
|
||||
|
||||
#include "archidekt_api_response_edition.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class ArchidektApiResponseCard
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
ArchidektApiResponseCard() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
QJsonObject getOracleCard() const
|
||||
{
|
||||
return oracleCard;
|
||||
};
|
||||
|
||||
QString getCollectorNumber() const
|
||||
{
|
||||
return collectorNumber;
|
||||
}
|
||||
|
||||
ArchidektApiResponseEdition getEdition() const
|
||||
{
|
||||
return edition;
|
||||
}
|
||||
|
||||
private:
|
||||
int id;
|
||||
QString artist;
|
||||
int tcgProductId;
|
||||
int ckFoilId;
|
||||
int ckNormalId;
|
||||
QString cmEd;
|
||||
QString scgSku;
|
||||
QString scgFoilSku;
|
||||
QString collectorNumber;
|
||||
int multiverseId;
|
||||
int mtgoFoilId;
|
||||
int mtgoNormalId;
|
||||
QString uid;
|
||||
QString displayName;
|
||||
QString releasedAt;
|
||||
ArchidektApiResponseEdition edition;
|
||||
QString flavor;
|
||||
QStringList games;
|
||||
QStringList options;
|
||||
QString scryfallImageHash;
|
||||
QJsonObject oracleCard;
|
||||
int owned;
|
||||
int pinnedStatus;
|
||||
// ArchidektApiResponsePrices prices;
|
||||
QString rarity;
|
||||
QStringList globalCategories;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "archidekt_api_response_card_entry.h"
|
||||
|
||||
void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toInt();
|
||||
|
||||
auto categoriesJson = json.value("categories").toArray();
|
||||
|
||||
for (auto category : categoriesJson) {
|
||||
categories.append(category.toString());
|
||||
}
|
||||
|
||||
companion = json.value("companion").toBool();
|
||||
flippedDefault = json.value("flippedDefault").toBool();
|
||||
label = json.value("label").toString();
|
||||
modifier = json.value("modifier").toString();
|
||||
quantity = json.value("quantity").toInt();
|
||||
customCmc = json.value("customCmc").toInt();
|
||||
// removedCategories = {""};
|
||||
createdAt = json.value("createdAt").toString();
|
||||
updatedAt = json.value("updatedAt").toString();
|
||||
deletedAt = json.value("deletedAt").toString();
|
||||
notes = json.value("notes").toString();
|
||||
card.fromJson(json.value("card").toObject());
|
||||
}
|
||||
|
||||
void ArchidektApiResponseCardEntry::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "Categories:" << categories;
|
||||
qDebug() << "Companion:" << companion;
|
||||
qDebug() << "FlippedDefault:" << flippedDefault;
|
||||
qDebug() << "Label:" << label;
|
||||
qDebug() << "Modifier:" << modifier;
|
||||
qDebug() << "Quantity:" << quantity;
|
||||
qDebug() << "CustomCmc:" << customCmc;
|
||||
qDebug() << "RemovedCategories:" << removedCategories;
|
||||
qDebug() << "CreatedAt:" << createdAt;
|
||||
qDebug() << "UpdatedAt:" << updatedAt;
|
||||
qDebug() << "DeletedAt:" << deletedAt;
|
||||
qDebug() << "Notes:" << notes;
|
||||
card.debugPrint();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H
|
||||
|
||||
#include "archidekt_api_response_card.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class ArchidektApiResponseCardEntry
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
ArchidektApiResponseCardEntry() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
ArchidektApiResponseCard getCard() const
|
||||
{
|
||||
return card;
|
||||
};
|
||||
|
||||
QStringList getCategories() const
|
||||
{
|
||||
return categories;
|
||||
}
|
||||
|
||||
int getQuantity() const
|
||||
{
|
||||
return quantity;
|
||||
}
|
||||
|
||||
private:
|
||||
int id;
|
||||
QStringList categories;
|
||||
bool companion;
|
||||
bool flippedDefault;
|
||||
QString label;
|
||||
QString modifier;
|
||||
int quantity;
|
||||
int customCmc;
|
||||
QStringList removedCategories;
|
||||
QString createdAt;
|
||||
QString updatedAt;
|
||||
QString deletedAt;
|
||||
QString notes;
|
||||
ArchidektApiResponseCard card;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "archidekt_api_response_edition.h"
|
||||
|
||||
void ArchidektApiResponseEdition::fromJson(const QJsonObject &json)
|
||||
{
|
||||
editionCode = json.value("editioncode").toString();
|
||||
editionName = json.value("editionname").toString();
|
||||
editionDate = json.value("editiondate").toString();
|
||||
editionType = json.value("editiontype").toString();
|
||||
mtgoCode = json.value("mtgocode").toString();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseEdition::debugPrint() const
|
||||
{
|
||||
qDebug() << "Edition Code: " << editionCode;
|
||||
qDebug() << "Edition Name: " << editionName;
|
||||
qDebug() << "Edition Date: " << editionDate;
|
||||
qDebug() << "Edition Type: " << editionType;
|
||||
qDebug() << "Mtgo Code: " << mtgoCode;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class ArchidektApiResponseEdition
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
ArchidektApiResponseEdition() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
[[nodiscard]] QString getEditionCode() const
|
||||
{
|
||||
return editionCode;
|
||||
}
|
||||
[[nodiscard]] QString getEditionName() const
|
||||
{
|
||||
return editionName;
|
||||
}
|
||||
[[nodiscard]] QString getEditionDate() const
|
||||
{
|
||||
return editionDate;
|
||||
}
|
||||
[[nodiscard]] QString getEditionType() const
|
||||
{
|
||||
return editionType;
|
||||
}
|
||||
[[nodiscard]] QString getMtgoCode() const
|
||||
{
|
||||
return mtgoCode;
|
||||
}
|
||||
|
||||
private:
|
||||
QString editionCode;
|
||||
QString editionName;
|
||||
QString editionDate;
|
||||
QString editionType;
|
||||
QString mtgoCode;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "archidekt_api_response_deck.h"
|
||||
|
||||
#include "../card/archidekt_api_response_card_entry.h"
|
||||
|
||||
void ArchidektApiResponseDeck::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toInt();
|
||||
name = json.value("name").toString();
|
||||
size = json.value("size").toInt();
|
||||
updatedAt = json.value("updatedAt").toString();
|
||||
createdAt = json.value("createdAt").toString();
|
||||
deckFormat = json.value("deckFormat").toInt();
|
||||
edhBracket = json.value("edhBracket").toInt();
|
||||
featured = json.value("featured").toString();
|
||||
customFeatured = json.value("customFeatured").toString();
|
||||
viewCount = json.value("viewCount").toInt();
|
||||
privateDeck = json.value("private").toBool();
|
||||
unlisted = json.value("unlisted").toBool();
|
||||
theoryCrafted = json.value("theoryCrafted").toBool();
|
||||
points = json.value("points").toInt();
|
||||
userInput = json.value("userInput").toInt();
|
||||
owner.fromJson(json.value("owner").toObject());
|
||||
commentRoot = json.value("commentRoot").toInt();
|
||||
editors = json.value("editors").toString();
|
||||
parentFolderId = json.value("parentFolderId").toInt();
|
||||
bookmarked = json.value("bookmarked").toBool();
|
||||
|
||||
auto categoriesJson = json.value("categories").toArray();
|
||||
for (auto category : categoriesJson) {
|
||||
ArchidektApiResponseDeckCategory categoryEntry;
|
||||
categoryEntry.fromJson(category.toObject());
|
||||
categories.append(categoryEntry);
|
||||
}
|
||||
|
||||
// deckTags = {""};
|
||||
playgroupDeckUrl = json.value("playgroupDeckUrl").toString();
|
||||
cardPackage = json.value("cardPackage").toString();
|
||||
|
||||
auto cardsObject = json.value("cards").toArray();
|
||||
|
||||
for (auto card : cardsObject) {
|
||||
ArchidektApiResponseCardEntry entry;
|
||||
entry.fromJson(card.toObject());
|
||||
cards.append(entry);
|
||||
}
|
||||
|
||||
// TODO but not really important
|
||||
// customCards = {""};
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeck::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "Name:" << name;
|
||||
qDebug() << "Size:" << size;
|
||||
qDebug() << "UpdatedAt:" << updatedAt;
|
||||
qDebug() << "CreatedAt:" << createdAt;
|
||||
qDebug() << "DeckFormat:" << deckFormat;
|
||||
qDebug() << "EdhBracket:" << edhBracket;
|
||||
qDebug() << "Featured:" << featured;
|
||||
qDebug() << "CustomFeatured:" << customFeatured;
|
||||
qDebug() << "ViewCount:" << viewCount;
|
||||
qDebug() << "Private:" << privateDeck;
|
||||
qDebug() << "Unlisted:" << unlisted;
|
||||
qDebug() << "TheoryCrafted:" << theoryCrafted;
|
||||
qDebug() << "Points:" << points;
|
||||
qDebug() << "UserInput:" << userInput;
|
||||
owner.debugPrint();
|
||||
qDebug() << "CommentRoot:" << commentRoot;
|
||||
qDebug() << "Editors:" << editors;
|
||||
qDebug() << "ParentFolderId:" << parentFolderId;
|
||||
qDebug() << "Bookmarked:" << bookmarked;
|
||||
qDebug() << "DeckTags:" << deckTags;
|
||||
qDebug() << "PlaygroupDeckUrl:" << playgroupDeckUrl;
|
||||
qDebug() << "CardPackage:" << cardPackage;
|
||||
for (auto card : cards) {
|
||||
card.debugPrint();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H
|
||||
|
||||
#include "../card/archidekt_api_response_card.h"
|
||||
#include "../card/archidekt_api_response_card_entry.h"
|
||||
#include "../deck_listings/archidekt_api_response_deck_owner.h"
|
||||
#include "archidekt_api_response_deck_category.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class ArchidektApiResponseDeck
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
ArchidektApiResponseDeck() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
QVector<ArchidektApiResponseCardEntry> getCards() const
|
||||
{
|
||||
return cards;
|
||||
};
|
||||
|
||||
QVector<ArchidektApiResponseDeckCategory> getCategories() const
|
||||
{
|
||||
return categories;
|
||||
}
|
||||
|
||||
QString getDeckName() const
|
||||
{
|
||||
return name;
|
||||
};
|
||||
|
||||
private:
|
||||
int id;
|
||||
QString name;
|
||||
int size;
|
||||
QString updatedAt;
|
||||
QString createdAt;
|
||||
int deckFormat;
|
||||
int edhBracket;
|
||||
QString featured;
|
||||
QString customFeatured;
|
||||
int viewCount;
|
||||
bool privateDeck;
|
||||
bool unlisted;
|
||||
bool theoryCrafted;
|
||||
int points;
|
||||
int userInput;
|
||||
ArchidektApiResponseDeckOwner owner;
|
||||
int commentRoot;
|
||||
QString editors;
|
||||
int parentFolderId;
|
||||
bool bookmarked;
|
||||
QVector<ArchidektApiResponseDeckCategory> categories;
|
||||
QStringList deckTags;
|
||||
QString playgroupDeckUrl;
|
||||
QString cardPackage;
|
||||
QVector<ArchidektApiResponseCardEntry> cards;
|
||||
QStringList customCards;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "archidekt_api_response_deck_category.h"
|
||||
|
||||
void ArchidektApiResponseDeckCategory::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toInt();
|
||||
name = json.value("name").toString();
|
||||
isPremier = json.value("isPremier").toBool();
|
||||
includedInDeck = json.value("includedInDeck").toBool();
|
||||
includedInPrice = json.value("includedInPrice").toBool();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckCategory::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "Name:" << name;
|
||||
qDebug() << "isPremier:" << isPremier;
|
||||
qDebug() << "IncludedInDeck:" << includedInDeck;
|
||||
qDebug() << "IncludedInPrice:" << includedInPrice;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H
|
||||
|
||||
#include "../card/archidekt_api_response_card.h"
|
||||
#include "../card/archidekt_api_response_card_entry.h"
|
||||
#include "../deck_listings/archidekt_api_response_deck_owner.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class ArchidektApiResponseDeckCategory
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
ArchidektApiResponseDeckCategory() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
[[nodiscard]] int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
[[nodiscard]] QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
[[nodiscard]] bool getIsPremier() const
|
||||
{
|
||||
return isPremier;
|
||||
}
|
||||
[[nodiscard]] bool getIncludedInDeck() const
|
||||
{
|
||||
return includedInDeck;
|
||||
}
|
||||
[[nodiscard]] bool getIncludedInPrice() const
|
||||
{
|
||||
return includedInPrice;
|
||||
}
|
||||
|
||||
private:
|
||||
int id;
|
||||
QString name;
|
||||
bool isPremier;
|
||||
bool includedInDeck;
|
||||
bool includedInPrice;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "archidekt_api_response_deck_listing_container.h"
|
||||
|
||||
#include "archidekt_api_response_deck_owner.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
void ArchidektApiResponseDeckListingContainer::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toInt();
|
||||
name = json.value("name").toString();
|
||||
size = json.value("size").toInt();
|
||||
updatedAt = json.value("updatedAt").toString();
|
||||
createdAt = json.value("createdAt").toString();
|
||||
deckFormat = json.value("deckFormat").toInt();
|
||||
edhBracket = json.value("edhBracket").toInt();
|
||||
featured = json.value("featured").toString();
|
||||
customFeatured = json.value("customFeatured").toString();
|
||||
viewCount = json.value("viewCount").toInt();
|
||||
privateDeck = json.value("private").toBool();
|
||||
unlisted = json.value("unlisted").toBool();
|
||||
theoryCrafted = json.value("theoryCrafted").toBool();
|
||||
game = json.value("game").toString();
|
||||
hasDescription = json.value("hasDescription").toBool();
|
||||
// TODO
|
||||
// tags = {""};
|
||||
parentFolderId = json.value("parentFolderId").toInt();
|
||||
owner.fromJson(json.value("owner").toObject());
|
||||
|
||||
auto colorsJson = json.value("colors").toObject();
|
||||
|
||||
for (auto color : colorsJson.keys()) {
|
||||
colors[color] = colorsJson[color].toInt();
|
||||
}
|
||||
|
||||
cardPackage = json.value("cardPackage").toString();
|
||||
contest = json.value("contest").toString();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckListingContainer::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "Name:" << name;
|
||||
qDebug() << "Size:" << size;
|
||||
qDebug() << "UpdatedAt:" << updatedAt;
|
||||
qDebug() << "CreatedAt:" << createdAt;
|
||||
qDebug() << "DeckFormat:" << deckFormat;
|
||||
qDebug() << "EdhBracket:" << edhBracket;
|
||||
qDebug() << "Featured:" << featured;
|
||||
qDebug() << "CustomFeatured:" << customFeatured;
|
||||
qDebug() << "ViewCount:" << viewCount;
|
||||
qDebug() << "Private:" << privateDeck;
|
||||
qDebug() << "Unlisted:" << unlisted;
|
||||
qDebug() << "TheoryCrafted:" << theoryCrafted;
|
||||
qDebug() << "Game:" << game;
|
||||
qDebug() << "HasDescription:" << hasDescription;
|
||||
qDebug() << "Tags:" << tags;
|
||||
qDebug() << "ParentFolderId:" << parentFolderId;
|
||||
owner.debugPrint();
|
||||
qDebug() << "Colors:" << colors;
|
||||
qDebug() << "CardPackage" << cardPackage;
|
||||
qDebug() << "Contest:" << contest;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H
|
||||
#define COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H
|
||||
|
||||
#include "archidekt_api_response_deck_owner.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class ArchidektApiResponseDeckListingContainer
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
ArchidektApiResponseDeckListingContainer() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
[[nodiscard]] int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
[[nodiscard]] QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
[[nodiscard]] int getSize() const
|
||||
{
|
||||
return size;
|
||||
}
|
||||
[[nodiscard]] QString getUpdatedAt() const
|
||||
{
|
||||
return updatedAt;
|
||||
}
|
||||
[[nodiscard]] QString getCreatedAt() const
|
||||
{
|
||||
return createdAt;
|
||||
}
|
||||
[[nodiscard]] int getDeckFormat() const
|
||||
{
|
||||
return deckFormat;
|
||||
}
|
||||
[[nodiscard]] int getEDHBracket() const
|
||||
{
|
||||
return edhBracket;
|
||||
}
|
||||
[[nodiscard]] QString getFeatured() const
|
||||
{
|
||||
return featured;
|
||||
}
|
||||
[[nodiscard]] QString getCustomFeatured() const
|
||||
{
|
||||
return customFeatured;
|
||||
}
|
||||
[[nodiscard]] int getViewCount() const
|
||||
{
|
||||
return viewCount;
|
||||
}
|
||||
[[nodiscard]] bool getPrivateDeck() const
|
||||
{
|
||||
return privateDeck;
|
||||
}
|
||||
[[nodiscard]] bool getUnlisted() const
|
||||
{
|
||||
return unlisted;
|
||||
}
|
||||
[[nodiscard]] bool getTheoryCrafted() const
|
||||
{
|
||||
return theoryCrafted;
|
||||
}
|
||||
[[nodiscard]] QString getGame() const
|
||||
{
|
||||
return game;
|
||||
}
|
||||
[[nodiscard]] bool getHasDescription() const
|
||||
{
|
||||
return hasDescription;
|
||||
}
|
||||
[[nodiscard]] QStringList getTags() const
|
||||
{
|
||||
return tags;
|
||||
}
|
||||
[[nodiscard]] int getParentFolderId() const
|
||||
{
|
||||
return parentFolderId;
|
||||
}
|
||||
[[nodiscard]] ArchidektApiResponseDeckOwner getOwner() const
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
[[nodiscard]] QMap<QString, int> getColors() const
|
||||
{
|
||||
return colors;
|
||||
}
|
||||
[[nodiscard]] QString getCardPackage() const
|
||||
{
|
||||
return cardPackage;
|
||||
}
|
||||
[[nodiscard]] QString getContest() const
|
||||
{
|
||||
return contest;
|
||||
}
|
||||
|
||||
private:
|
||||
int id;
|
||||
QString name;
|
||||
int size;
|
||||
QString updatedAt;
|
||||
QString createdAt;
|
||||
int deckFormat;
|
||||
int edhBracket;
|
||||
QString featured;
|
||||
QString customFeatured;
|
||||
int viewCount;
|
||||
bool privateDeck;
|
||||
bool unlisted;
|
||||
bool theoryCrafted;
|
||||
QString game;
|
||||
bool hasDescription;
|
||||
QStringList tags;
|
||||
int parentFolderId;
|
||||
ArchidektApiResponseDeckOwner owner;
|
||||
QMap<QString, int> colors;
|
||||
QString cardPackage;
|
||||
QString contest;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "archidekt_api_response_deck_owner.h"
|
||||
|
||||
void ArchidektApiResponseDeckOwner::fromJson(const QJsonObject &json)
|
||||
{
|
||||
id = json.value("id").toInt();
|
||||
userName = json.value("username").toString();
|
||||
avatar = QUrl(json.value("avatar").toString());
|
||||
moderator = json.value("moderator").toBool();
|
||||
pledgeLevel = json.value("pledgeLevel").toInt();
|
||||
// TODO but not really important
|
||||
// roles = {""};
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckOwner::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "UserName:" << userName;
|
||||
qDebug() << "Avatar:" << avatar;
|
||||
qDebug() << "Moderator:" << moderator;
|
||||
qDebug() << "PledgeLevel:" << pledgeLevel;
|
||||
qDebug() << "Roles:" << roles;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class ArchidektApiResponseDeckOwner
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
ArchidektApiResponseDeckOwner() = default;
|
||||
|
||||
// Parse deck-related data from JSON
|
||||
void fromJson(const QJsonObject &json);
|
||||
|
||||
// Debug method for logging
|
||||
void debugPrint() const;
|
||||
|
||||
[[nodiscard]] QString getName() const
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
|
||||
private:
|
||||
int id;
|
||||
QString userName;
|
||||
QUrl avatar;
|
||||
bool moderator;
|
||||
int pledgeLevel;
|
||||
QStringList roles;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H
|
||||
@@ -0,0 +1,157 @@
|
||||
#include "archidekt_api_response_deck_display_widget.h"
|
||||
|
||||
#include "../../../../../deck_loader/deck_loader.h"
|
||||
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../../../../cards/card_size_widget.h"
|
||||
#include "../../../../cards/deck_card_zone_display_widget.h"
|
||||
#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h"
|
||||
#include "../api_response/deck/archidekt_api_response_deck.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
||||
ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent,
|
||||
ArchidektApiResponseDeck _response,
|
||||
CardSizeWidget *_cardSizeSlider)
|
||||
: QWidget(parent), response(_response), cardSizeSlider(_cardSizeSlider)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
openInEditorButton = new QPushButton(this);
|
||||
layout->addWidget(openInEditorButton);
|
||||
|
||||
connect(openInEditorButton, &QPushButton::clicked, this,
|
||||
&ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor);
|
||||
|
||||
displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this);
|
||||
layout->addWidget(displayOptionsWidget);
|
||||
|
||||
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, this,
|
||||
&ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange);
|
||||
|
||||
scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
|
||||
layout->addWidget(scrollArea);
|
||||
|
||||
container = new QWidget(scrollArea);
|
||||
|
||||
scrollArea->setWidget(container);
|
||||
|
||||
containerLayout = new QVBoxLayout(container);
|
||||
container->setLayout(containerLayout);
|
||||
|
||||
zoneContainer = new QWidget(container);
|
||||
containerLayout->addWidget(zoneContainer);
|
||||
zoneContainerLayout = new QVBoxLayout(zoneContainer);
|
||||
zoneContainer->setLayout(zoneContainerLayout);
|
||||
|
||||
QString tempDeck;
|
||||
QTextStream deckStream(&tempDeck);
|
||||
|
||||
for (auto card : response.getCards()) {
|
||||
QString fullName = card.getCard().getOracleCard().value("name").toString();
|
||||
// We don't really care about the second card, the card database already has it as a relation
|
||||
QString cleanName = fullName.split("//").first().trimmed();
|
||||
|
||||
tempDeck += QString("%1 %2 (%3) %4\n")
|
||||
.arg(card.getQuantity())
|
||||
.arg(cleanName)
|
||||
.arg(card.getCard().getEdition().getEditionCode().toUpper())
|
||||
.arg(card.getCard().getCollectorNumber());
|
||||
}
|
||||
|
||||
model = new DeckListModel(this);
|
||||
connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset);
|
||||
model->getDeckList()->loadFromStream_Plain(deckStream, false);
|
||||
|
||||
DeckLoader::resolveSetNameAndNumberToProviderID(model->getDeckList());
|
||||
|
||||
model->rebuildTree();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::retranslateUi()
|
||||
{
|
||||
openInEditorButton->setText(tr("Open Deck in Deck Editor"));
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString &activeGroupCriteria)
|
||||
{
|
||||
model->setActiveGroupCriteria(DeckListModelGroupCriteria::fromString(activeGroupCriteria));
|
||||
model->sort(1, Qt::AscendingOrder);
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor()
|
||||
{
|
||||
auto loader = new DeckLoader(this);
|
||||
loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native());
|
||||
|
||||
loader->getDeckList()->setName(response.getDeckName());
|
||||
|
||||
emit openInDeckEditor(loader);
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets()
|
||||
{
|
||||
for (auto idx : indexToWidgetMap.keys()) {
|
||||
auto displayWidget = indexToWidgetMap.value(idx);
|
||||
zoneContainerLayout->removeWidget(displayWidget);
|
||||
indexToWidgetMap.remove(idx);
|
||||
delete displayWidget;
|
||||
}
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::decklistModelReset()
|
||||
{
|
||||
clearAllDisplayWidgets();
|
||||
constructZoneWidgetsFromDeckListModel();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListModel()
|
||||
{
|
||||
qDebug() << model->rowCount(model->getRoot());
|
||||
QSortFilterProxyModel proxy;
|
||||
proxy.setSourceModel(model);
|
||||
proxy.setSortRole(Qt::EditRole);
|
||||
proxy.sort(1, Qt::AscendingOrder);
|
||||
|
||||
for (int i = 0; i < proxy.rowCount(); ++i) {
|
||||
QModelIndex proxyIndex = proxy.index(i, 0);
|
||||
QModelIndex sourceIndex = proxy.mapToSource(proxyIndex);
|
||||
|
||||
// Make a persistent index from the *source* model
|
||||
QPersistentModelIndex persistent(sourceIndex);
|
||||
|
||||
if (indexToWidgetMap.contains(persistent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DeckCardZoneDisplayWidget *zoneDisplayWidget =
|
||||
new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent,
|
||||
model->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(),
|
||||
"maintype", {"name"}, DisplayType::Overlap, 20, 10, cardSizeSlider);
|
||||
|
||||
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, zoneDisplayWidget,
|
||||
&DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged);
|
||||
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, zoneDisplayWidget,
|
||||
&DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged);
|
||||
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::displayTypeChanged, zoneDisplayWidget,
|
||||
&DeckCardZoneDisplayWidget::refreshDisplayType);
|
||||
zoneContainerLayout->addWidget(zoneDisplayWidget);
|
||||
|
||||
indexToWidgetMap.insert(persistent, zoneDisplayWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
layout->invalidate();
|
||||
layout->activate();
|
||||
layout->update();
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../../deck_loader/deck_loader.h"
|
||||
#include "../../../../cards/card_size_widget.h"
|
||||
#include "../../../../general/layout_containers/flow_widget.h"
|
||||
#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h"
|
||||
#include "../api_response/deck/archidekt_api_response_deck.h"
|
||||
#include "deck_list_model.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* @class ArchidektApiResponseDeckDisplayWidget
|
||||
* @brief Displays a full deck fetched from an Archidekt API response.
|
||||
*
|
||||
* This widget visualizes all cards in a deck retrieved from the Archidekt API.
|
||||
* It supports:
|
||||
* - Interactive display options via a VisualDeckDisplayOptionsWidget.
|
||||
* - Scrollable display of deck zones/cards with DeckCardZoneDisplayWidget.
|
||||
* - Integration with a CardSizeWidget slider for card scaling.
|
||||
* - Opening the deck in a deck editor.
|
||||
*
|
||||
* The widget internally constructs a DeckListModel from the Archidekt API response,
|
||||
* then builds zone widgets for each group of cards according to the active group
|
||||
* criteria. It also responds dynamically to model resets or sorting/grouping changes.
|
||||
*
|
||||
* ### Signals
|
||||
* - `requestNavigation(QString url)` — triggered when navigation to a deck URL is requested.
|
||||
* - `openInDeckEditor(DeckLoader *loader)` — emitted when the user chooses to open the deck
|
||||
* in the deck editor.
|
||||
*
|
||||
* ### Features
|
||||
* - Automatically generates DeckCardZoneDisplayWidget instances for each card group.
|
||||
* - Provides a scrollable layout for decks of arbitrary size.
|
||||
* - Updates layouts dynamically when resized or when display/group/sort criteria change.
|
||||
*/
|
||||
class ArchidektApiResponseDeckDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when navigation to a deck URL is requested.
|
||||
* @param url URL of the deck on Archidekt.
|
||||
*/
|
||||
void requestNavigation(QString url);
|
||||
|
||||
/**
|
||||
* @brief Emitted when the deck should be opened in the deck editor.
|
||||
* @param loader Initialized DeckLoader containing the deck data.
|
||||
*/
|
||||
void openInDeckEditor(DeckLoader *loader);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a display widget for an Archidekt deck.
|
||||
* @param parent Parent widget.
|
||||
* @param response API deck data container.
|
||||
* @param cardSizeSlider Slider controlling card scaling.
|
||||
*/
|
||||
explicit ArchidektApiResponseDeckDisplayWidget(QWidget *parent,
|
||||
ArchidektApiResponseDeck response,
|
||||
CardSizeWidget *cardSizeSlider);
|
||||
|
||||
/**
|
||||
* @brief Updates all UI text for retranslation/localization.
|
||||
*
|
||||
* Called when the application language changes.
|
||||
*/
|
||||
void retranslateUi();
|
||||
|
||||
/**
|
||||
* @brief Opens the deck in the deck editor via DeckLoader.
|
||||
*/
|
||||
void actOpenInDeckEditor();
|
||||
|
||||
/**
|
||||
* @brief Clears all dynamically generated card zone display widgets.
|
||||
*/
|
||||
void clearAllDisplayWidgets();
|
||||
|
||||
/**
|
||||
* @brief Handles model reset by clearing and reconstructing display widgets.
|
||||
*/
|
||||
void decklistModelReset();
|
||||
|
||||
/**
|
||||
* @brief Builds DeckCardZoneDisplayWidget instances from the current DeckListModel.
|
||||
*/
|
||||
void constructZoneWidgetsFromDeckListModel();
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Slot triggered when the active group criteria change.
|
||||
* @param activeGroupCriteria Name of the new grouping criteria.
|
||||
*/
|
||||
void onGroupCriteriaChange(const QString &activeGroupCriteria);
|
||||
|
||||
private:
|
||||
ArchidektApiResponseDeck response; ///< API deck data container
|
||||
CardSizeWidget *cardSizeSlider; ///< Slider for adjusting card sizes
|
||||
QVBoxLayout *layout; ///< Main vertical layout
|
||||
QPushButton *openInEditorButton; ///< Button to open deck in editor
|
||||
VisualDeckDisplayOptionsWidget *displayOptionsWidget; ///< Controls grouping/sorting/display
|
||||
QScrollArea *scrollArea; ///< Scrollable area for deck zones
|
||||
QWidget *zoneContainer; ///< Container for deck zones
|
||||
QVBoxLayout *zoneContainerLayout; ///< Layout for deck zones
|
||||
QWidget *container; ///< Outer container for scroll area
|
||||
QHash<QPersistentModelIndex, QWidget *> indexToWidgetMap; ///< Maps model indices to widgets
|
||||
QVBoxLayout *containerLayout; ///< Layout for container
|
||||
DeckListModel *model; ///< Deck list model
|
||||
protected slots:
|
||||
/**
|
||||
* @brief Updates layout and display on resize.
|
||||
* @param event Resize event.
|
||||
*/
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H
|
||||
@@ -0,0 +1,235 @@
|
||||
#include "archidekt_api_response_deck_entry_display_widget.h"
|
||||
|
||||
#include "../../../../../card_picture_loader/card_picture_loader.h"
|
||||
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../../../../general/display/background_plate_widget.h"
|
||||
#include "../../../../general/display/color_bar.h"
|
||||
#include "archidekt_deck_preview_image_display_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
|
||||
#define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg"
|
||||
|
||||
QString timeAgo(const QString ×tamp)
|
||||
{
|
||||
QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate);
|
||||
|
||||
if (!dt.isValid())
|
||||
return timestamp; // fallback if parsing fails
|
||||
|
||||
qint64 secs = dt.secsTo(QDateTime::currentDateTimeUtc());
|
||||
|
||||
if (secs < 60)
|
||||
return QString("%1 seconds ago").arg(secs);
|
||||
if (secs < 3600)
|
||||
return QString("%1 minutes ago").arg(secs / 60);
|
||||
if (secs < 86400)
|
||||
return QString("%1 hours ago").arg(secs / 3600);
|
||||
if (secs < 30 * 86400)
|
||||
return QString("%1 days ago").arg(secs / 86400);
|
||||
if (secs < 365 * 86400)
|
||||
return QString("%1 months ago").arg(secs / (30 * 86400));
|
||||
|
||||
return QString("%1 years ago").arg(secs / (365 * 86400));
|
||||
}
|
||||
|
||||
ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplayWidget(
|
||||
QWidget *parent,
|
||||
ArchidektApiResponseDeckListingContainer _response,
|
||||
QNetworkAccessManager *_imageNetworkManager)
|
||||
: QWidget(parent), response(_response), imageNetworkManager(_imageNetworkManager)
|
||||
{
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
this->setMaximumWidth(400);
|
||||
this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
|
||||
auto headerLayout = new QVBoxLayout();
|
||||
|
||||
previewWidget = new ArchidektDeckPreviewImageDisplayWidget(this);
|
||||
|
||||
previewWidget->setMaximumWidth(400);
|
||||
previewWidget->setMinimumHeight(300); // consistent height
|
||||
|
||||
// Set deck name (ellided)
|
||||
QFontMetrics fm(previewWidget->topLeftLabel->font());
|
||||
QString elided = fm.elidedText(response.getName(), Qt::ElideRight, 280);
|
||||
previewWidget->topLeftLabel->setLabelText(elided);
|
||||
previewWidget->topLeftLabel->setToolTip(response.getName());
|
||||
|
||||
// Set count
|
||||
previewWidget->topRightLabel->setLabelText(QString::number(response.getSize()));
|
||||
|
||||
// EDH bracket (skip if 0)
|
||||
if (response.getEDHBracket() != 0) {
|
||||
previewWidget->bottomLeftLabel->setLabelText(QString("EDH: %1").arg(response.getEDHBracket()));
|
||||
} else {
|
||||
previewWidget->bottomLeftLabel->hide();
|
||||
}
|
||||
|
||||
// Views
|
||||
previewWidget->bottomRightLabel->setLabelText(QString("Views: %1").arg(response.getViewCount()));
|
||||
|
||||
// Use preview->imageLabel for image loading
|
||||
picture = previewWidget->imageLabel;
|
||||
|
||||
imageUrl = response.getFeatured().isEmpty() ? QUrl(ARCHIDEKT_DEFAULT_IMAGE) : QUrl(response.getFeatured());
|
||||
|
||||
QNetworkRequest req(imageUrl);
|
||||
QNetworkReply *reply = imageNetworkManager->get(req);
|
||||
|
||||
// tag the reply with "this" so we know it belongs to us later
|
||||
reply->setProperty("deckWidget", QVariant::fromValue<void *>(this));
|
||||
reply->setProperty("requestedUrl", imageUrl);
|
||||
|
||||
connect(imageNetworkManager, &QNetworkAccessManager::finished, this,
|
||||
&ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished);
|
||||
|
||||
headerLayout->addWidget(previewWidget);
|
||||
|
||||
auto colors = response.getColors();
|
||||
|
||||
ColorBar *colorBar = new ColorBar(colors, this);
|
||||
colorBar->setMinPercentThreshold(3);
|
||||
colorBar->setFixedHeight(22);
|
||||
|
||||
headerLayout->addWidget(colorBar);
|
||||
|
||||
// Create a shared plate for the labels
|
||||
backgroundPlateWidget = new BackgroundPlateWidget(this);
|
||||
backgroundPlateWidget->setFixedHeight(120); // Adjust height to fit all labels
|
||||
|
||||
QVBoxLayout *plateLayout = new QVBoxLayout(backgroundPlateWidget);
|
||||
|
||||
// Add labels to the plate layout
|
||||
QLabel *ownerLabel = new QLabel(QString("Owner: %1").arg(response.getOwner().getName()));
|
||||
plateLayout->addWidget(ownerLabel);
|
||||
|
||||
QLabel *createdAtLabel = new QLabel(QString("Created: %1").arg(timeAgo(response.getCreatedAt())));
|
||||
plateLayout->addWidget(createdAtLabel);
|
||||
|
||||
QLabel *updatedAtLabel = new QLabel(QString("Updated: %1").arg(timeAgo(response.getUpdatedAt())));
|
||||
plateLayout->addWidget(updatedAtLabel);
|
||||
|
||||
// Add the shared plate to the header layout
|
||||
headerLayout->addWidget(backgroundPlateWidget);
|
||||
|
||||
layout->addLayout(headerLayout);
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mousePressEvent(event);
|
||||
actRequestNavigationToDeck();
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::enterEvent(QEnterEvent *event)
|
||||
#else
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
QWidget::enterEvent(event);
|
||||
backgroundPlateWidget->setFocused(true);
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::leaveEvent(QEvent *event)
|
||||
{
|
||||
QWidget::leaveEvent(event);
|
||||
backgroundPlateWidget->setFocused(false);
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor(int scale)
|
||||
{
|
||||
scaleFactor = scale;
|
||||
updateScaledPreview();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished(QNetworkReply *reply)
|
||||
{
|
||||
// Check if this is our reply
|
||||
void *owner = reply->property("deckWidget").value<void *>();
|
||||
if (owner != this) {
|
||||
return; // not our reply
|
||||
}
|
||||
|
||||
// Check that the requested URL matches what we asked
|
||||
QUrl requestedUrl = reply->property("requestedUrl").toUrl();
|
||||
if (requestedUrl != imageUrl) {
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QPixmap loaded;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError || !loaded.loadFromData(reply->readAll())) {
|
||||
CardPictureLoader::getCardBackLoadingFailedPixmap(loaded, QSize(400, 400));
|
||||
}
|
||||
|
||||
originalPixmap = loaded;
|
||||
|
||||
// Always scale preview widget to this ratio
|
||||
previewWidget->setAspectRatio(DESIGN_RATIO);
|
||||
previewWidget->setPreviewWidth(400);
|
||||
|
||||
// Initial scaling
|
||||
updateScaledPreview();
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::updateScaledPreview()
|
||||
{
|
||||
if (originalPixmap.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int baseWidth = 400;
|
||||
int newWidth = baseWidth * scaleFactor / 100;
|
||||
int newHeight = static_cast<int>(newWidth * DESIGN_RATIO);
|
||||
|
||||
previewWidget->setFixedSize(newWidth, newHeight);
|
||||
|
||||
// Scale image to fill the preview area (crop edges)
|
||||
QPixmap scaled =
|
||||
originalPixmap.scaled(newWidth, newHeight, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
|
||||
// Crop to exact target size
|
||||
QRect cropRect((scaled.width() - newWidth) / 2, (scaled.height() - newHeight) / 2, newWidth, newHeight);
|
||||
QPixmap cropped = scaled.copy(cropRect);
|
||||
|
||||
picture->setPixmap(cropped);
|
||||
picture->setFixedSize(newWidth, newHeight);
|
||||
|
||||
// Update the elided deck name based on new width
|
||||
int textMaxWidth = int(newWidth * 0.7); // allow 70% of width for text
|
||||
QFontMetrics fm(previewWidget->topLeftLabel->font());
|
||||
QString elided = fm.elidedText(response.getName(), Qt::ElideRight, textMaxWidth);
|
||||
previewWidget->topLeftLabel->setText(elided);
|
||||
previewWidget->topLeftLabel->setToolTip(response.getName());
|
||||
|
||||
setFixedWidth(newWidth);
|
||||
|
||||
layout->invalidate();
|
||||
layout->activate();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
layout->invalidate();
|
||||
layout->activate();
|
||||
layout->update();
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckEntryDisplayWidget::actRequestNavigationToDeck()
|
||||
{
|
||||
emit requestNavigation(QString("https://archidekt.com/api/decks/%1/").arg(response.getId()));
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "../api_response/deck_listings/archidekt_api_response_deck_listing_container.h"
|
||||
#include "archidekt_deck_preview_image_display_widget.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QResizeEvent>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class BackgroundPlateWidget;
|
||||
|
||||
/**
|
||||
* @class ArchidektApiResponseDeckEntryDisplayWidget
|
||||
* @brief Displays a single Archidekt deck listing as a preview card with metadata.
|
||||
*
|
||||
* This widget renders a deck entry received from an Archidekt API response. It includes:
|
||||
* - A scaled deck preview image loaded asynchronously via QNetworkAccessManager.
|
||||
* - Elided deck name in the top-left corner.
|
||||
* - Deck size, EDH bracket, and view count labels.
|
||||
* - A color distribution bar summarizing deck colors.
|
||||
* - Metadata labels including owner, creation date, and last update date.
|
||||
*
|
||||
* The widget dynamically scales the preview image and labels according to a linked
|
||||
* CardSizeWidget slider. Hovering over the widget highlights the background plate,
|
||||
* and clicking emits a `requestNavigation` signal pointing to the deck URL.
|
||||
*
|
||||
* ### Features
|
||||
* - Asynchronous image loading with fallback to a default placeholder image.
|
||||
* - Maintains a fixed aspect ratio for the preview image (150:267).
|
||||
* - Updates text elision dynamically when resized or scaled.
|
||||
* - Integrates with FlowWidget containers for scrollable deck galleries.
|
||||
*
|
||||
* ### Signals
|
||||
* - `requestNavigation(QString url)` — emitted when the widget is clicked to request
|
||||
* navigation to the deck's Archidekt page.
|
||||
*
|
||||
* ### Slots
|
||||
* - `actRequestNavigationToDeck()` — triggers navigation.
|
||||
* - `setScaleFactor(int scale)` — adjusts preview image scaling.
|
||||
*/
|
||||
class ArchidektApiResponseDeckEntryDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when the user requests navigation.
|
||||
* @param url Full URL to the Archidekt page.
|
||||
*/
|
||||
void requestNavigation(QString url);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a deck entry display widget.
|
||||
* @param parent Parent widget.
|
||||
* @param response API container holding deck listing data.
|
||||
* @param imageNetworkManager Shared network manager for fetching preview images.
|
||||
*/
|
||||
explicit ArchidektApiResponseDeckEntryDisplayWidget(QWidget *parent,
|
||||
ArchidektApiResponseDeckListingContainer response,
|
||||
QNetworkAccessManager *imageNetworkManager);
|
||||
|
||||
/**
|
||||
* @brief Handles finished network replies for preview images.
|
||||
* @param reply QNetworkReply containing image data.
|
||||
*
|
||||
* Validates that the reply corresponds to this widget and updates the preview image.
|
||||
*/
|
||||
void onPreviewImageLoadFinished(QNetworkReply *reply);
|
||||
|
||||
/**
|
||||
* @brief Updates the scaled preview image and adjusts layout accordingly.
|
||||
*/
|
||||
void updateScaledPreview();
|
||||
|
||||
/**
|
||||
* @brief Ensures layout responds correctly on resize events.
|
||||
* @param event Resize event.
|
||||
*/
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Emits `requestNavigation` for the deck's URL.
|
||||
*/
|
||||
void actRequestNavigationToDeck();
|
||||
|
||||
/**
|
||||
* @brief Sets a scaling factor (percentage) for the preview image.
|
||||
* @param scale Scale percentage (100 = normal size).
|
||||
*/
|
||||
void setScaleFactor(int scale);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void enterEvent(QEnterEvent *event) override; ///< Qt6 hover enter
|
||||
#else
|
||||
void enterEvent(QEvent *event) override; ///< Qt5 hover enter
|
||||
#endif
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout; ///< Main vertical layout
|
||||
ArchidektApiResponseDeckListingContainer response; ///< Deck data
|
||||
QUrl imageUrl; ///< URL of the deck's preview image
|
||||
QNetworkAccessManager *imageNetworkManager; ///< Shared network manager
|
||||
ArchidektDeckPreviewImageDisplayWidget *previewWidget; ///< Widget showing the deck preview
|
||||
QLabel *picture; ///< QLabel displaying the scaled pixmap
|
||||
QPixmap originalPixmap; ///< Original image for scaling (avoids degradation)
|
||||
int scaleFactor = 100; ///< Current scaling percentage
|
||||
BackgroundPlateWidget *backgroundPlateWidget; ///< Plate for metadata labels
|
||||
static constexpr float DESIGN_RATIO = 150.0f / 267.0f; ///< Design aspect ratio
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "archidekt_api_response_deck_listings_display_widget.h"
|
||||
|
||||
#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
|
||||
#include "archidekt_api_response_deck_entry_display_widget.h"
|
||||
|
||||
ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsDisplayWidget(
|
||||
QWidget *parent,
|
||||
ArchidektDeckListingApiResponse response,
|
||||
CardSizeWidget *_cardSizeSlider)
|
||||
: QWidget(parent), cardSizeSlider(_cardSizeSlider)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||
|
||||
imageNetworkManager = new QNetworkAccessManager(this);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout
|
||||
#endif
|
||||
|
||||
imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
|
||||
|
||||
// Add widgets for deck listings
|
||||
auto deckListings = response.results;
|
||||
for (const auto &deckListing : deckListings) {
|
||||
auto cardListDisplayWidget =
|
||||
new ArchidektApiResponseDeckEntryDisplayWidget(this, deckListing, imageNetworkManager);
|
||||
cardListDisplayWidget->setScaleFactor(cardSizeSlider->getSlider()->value());
|
||||
connect(cardListDisplayWidget, &ArchidektApiResponseDeckEntryDisplayWidget::requestNavigation, this,
|
||||
&ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation);
|
||||
connect(cardSizeSlider->getSlider(), &QSlider::valueChanged, cardListDisplayWidget,
|
||||
&ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor);
|
||||
flowWidget->addWidget(cardListDisplayWidget);
|
||||
}
|
||||
|
||||
layout->addWidget(flowWidget);
|
||||
}
|
||||
|
||||
void ArchidektApiResponseDeckListingsDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
layout->invalidate();
|
||||
layout->activate();
|
||||
layout->update();
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H
|
||||
#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../cards/card_size_widget.h"
|
||||
#include "../../../../general/layout_containers/flow_widget.h"
|
||||
#include "../api_response/archidekt_deck_listing_api_response.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* @class ArchidektApiResponseDeckListingsDisplayWidget
|
||||
* @brief Displays a scrollable horizontal list of Archidekt deck listings with dynamic card sizing.
|
||||
*
|
||||
* This widget serves as a container for multiple
|
||||
* ArchidektApiResponseDeckEntryDisplayWidget instances, each representing one deck listing
|
||||
* returned from an Archidekt API call.
|
||||
*
|
||||
* ### Responsibilities
|
||||
* - Creates a **FlowWidget** that arranges deck entries horizontally with wrapping.
|
||||
* - Creates a shared **QNetworkAccessManager** for entry widgets to fetch card thumbnails.
|
||||
* - Connects a **CardSizeWidget** slider to all deck entries to dynamically rescale their preview cards.
|
||||
* - Propagates deck-navigation requests (`requestNavigation`) from children to the parent.
|
||||
*
|
||||
* ### Layout
|
||||
* The widget uses a single `QHBoxLayout` containing the `FlowWidget`.
|
||||
* The FlowWidget automatically manages child flow and scrollbar behavior (horizontal = off,
|
||||
* vertical = auto), providing an efficient scrollable gallery of deck entries.
|
||||
*
|
||||
* ### API Integration
|
||||
* The constructor consumes an `ArchidektDeckListingApiResponse`, iterates through its
|
||||
* `results`, and instantiates a child entry widget for each deck.
|
||||
*
|
||||
* ### Signals
|
||||
* - `requestNavigation(QString url)` — emitted whenever a child entry widget requests
|
||||
* navigation to a deck or related Archidekt page.
|
||||
*
|
||||
* ### Performance Notes
|
||||
* - All entry widgets share a single QNetworkAccessManager instance to reuse connections
|
||||
* and avoid redundant session creation.
|
||||
* - `resizeEvent()` forces layout invalidation to ensure the flow layout responds properly
|
||||
* to container resizing.
|
||||
*/
|
||||
class ArchidektApiResponseDeckListingsDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when a child deck entry requests that the UI navigate to a particular URL.
|
||||
* @param url The destination URL (typically an Archidekt deck page).
|
||||
*/
|
||||
void requestNavigation(QString url);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a widget that displays multiple deck listing previews.
|
||||
*
|
||||
* @param parent Parent widget.
|
||||
* @param response The Archidekt API response containing deck listings.
|
||||
* @param cardSizeSlider A slider widget used to dynamically resize card previews.
|
||||
*
|
||||
* Each deck in `response.results` becomes its own
|
||||
* ArchidektApiResponseDeckEntryDisplayWidget, added to the FlowWidget.
|
||||
*/
|
||||
explicit ArchidektApiResponseDeckListingsDisplayWidget(QWidget *parent,
|
||||
ArchidektDeckListingApiResponse response,
|
||||
CardSizeWidget *cardSizeSlider);
|
||||
|
||||
/**
|
||||
* @brief Ensures FlowWidget layout properly recomputes on resize.
|
||||
*
|
||||
* Forces the layout to invalidate and activate itself so that the
|
||||
* FlowWidget recalculates wrapping and child placement.
|
||||
*
|
||||
* @param event Resize event.
|
||||
*/
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
/// Slider controlling the scale of card thumbnails in all deck entry widgets.
|
||||
CardSizeWidget *cardSizeSlider;
|
||||
|
||||
/// Main horizontal layout containing the FlowWidget.
|
||||
QHBoxLayout *layout;
|
||||
|
||||
/// Container providing scrollable multi-row flow layout of deck entries.
|
||||
FlowWidget *flowWidget;
|
||||
|
||||
/// Shared network manager used to download card images for all child entry widgets.
|
||||
QNetworkAccessManager *imageNetworkManager;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "archidekt_deck_preview_image_display_widget.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QPainter>
|
||||
|
||||
ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
imageLabel = new QLabel(this);
|
||||
imageLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
topLeftLabel = new ShadowBackgroundLabel(this, "");
|
||||
topRightLabel = new ShadowBackgroundLabel(this, "");
|
||||
bottomLeftLabel = new ShadowBackgroundLabel(this, "");
|
||||
bottomRightLabel = new ShadowBackgroundLabel(this, "");
|
||||
|
||||
QFont f;
|
||||
f.setBold(true);
|
||||
f.setPointSize(16);
|
||||
topLeftLabel->setFont(f);
|
||||
topRightLabel->setFont(f);
|
||||
bottomLeftLabel->setFont(f);
|
||||
bottomRightLabel->setFont(f);
|
||||
|
||||
// Raise so labels appear above image
|
||||
topLeftLabel->raise();
|
||||
topRightLabel->raise();
|
||||
bottomLeftLabel->raise();
|
||||
bottomRightLabel->raise();
|
||||
}
|
||||
|
||||
void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio)
|
||||
{
|
||||
aspectRatio = ratio;
|
||||
}
|
||||
|
||||
void ArchidektDeckPreviewImageDisplayWidget::setPreviewWidth(int width)
|
||||
{
|
||||
int height = int(width * aspectRatio);
|
||||
|
||||
setFixedSize(width, height);
|
||||
updateGeometry();
|
||||
update();
|
||||
}
|
||||
|
||||
void ArchidektDeckPreviewImageDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
// Full size for the image
|
||||
imageLabel->setGeometry(rect());
|
||||
|
||||
topLeftLabel->adjustSize();
|
||||
topRightLabel->adjustSize();
|
||||
bottomLeftLabel->adjustSize();
|
||||
bottomRightLabel->adjustSize();
|
||||
|
||||
// Padding settings
|
||||
const int horizontalPadding = 8;
|
||||
const int topPadding = 6;
|
||||
const int bottomPadding = 6;
|
||||
|
||||
// Left-aligned, top-aligned (Deck Name)
|
||||
topLeftLabel->move(horizontalPadding, topPadding);
|
||||
|
||||
// Right-aligned, top-aligned (Card Count)
|
||||
topRightLabel->move(width() - topRightLabel->width() - horizontalPadding, topPadding);
|
||||
|
||||
// Bottom-left, bottom-aligned (EDH bracket)
|
||||
bottomLeftLabel->move(horizontalPadding, height() - bottomLeftLabel->height() - bottomPadding);
|
||||
|
||||
// Bottom-right, bottom-aligned (views)
|
||||
bottomRightLabel->move(width() - bottomRightLabel->width() - horizontalPadding,
|
||||
height() - bottomRightLabel->height() - bottomPadding);
|
||||
|
||||
// Ensure labels stay above image
|
||||
topLeftLabel->raise();
|
||||
topRightLabel->raise();
|
||||
bottomLeftLabel->raise();
|
||||
bottomRightLabel->raise();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H
|
||||
#define COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../general/display/shadow_background_label.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* @class ArchidektDeckPreviewImageDisplayWidget
|
||||
* @brief Widget for displaying a deck preview image with overlaid metadata labels.
|
||||
*
|
||||
* This widget shows a deck's preview image along with several overlay labels:
|
||||
* - Top-left: Deck name.
|
||||
* - Top-right: Card count.
|
||||
* - Bottom-left: EDH bracket (if applicable).
|
||||
* - Bottom-right: View count.
|
||||
*
|
||||
* Labels automatically scale and position themselves relative to the widget's size.
|
||||
* The image can be scaled while maintaining a specified aspect ratio.
|
||||
*
|
||||
* ### Features
|
||||
* - Adjustable preview width and aspect ratio.
|
||||
* - Labels automatically repositioned on resize.
|
||||
* - Supports overlaying multiple pieces of metadata with shadowed labels for readability.
|
||||
*/
|
||||
class ArchidektDeckPreviewImageDisplayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs the deck preview display widget.
|
||||
* @param parent Optional parent widget.
|
||||
*/
|
||||
explicit ArchidektDeckPreviewImageDisplayWidget(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Sets the aspect ratio for the preview image (height / width).
|
||||
* @param ratio Aspect ratio to maintain.
|
||||
*/
|
||||
void setAspectRatio(float ratio);
|
||||
|
||||
/**
|
||||
* @brief Sets the width of the preview image; height is adjusted according to the aspect ratio.
|
||||
* @param width Desired width in pixels.
|
||||
*/
|
||||
void setPreviewWidth(int width);
|
||||
|
||||
QLabel *imageLabel; ///< QLabel to display the deck image
|
||||
ShadowBackgroundLabel *topLeftLabel; ///< Overlay label at top-left (deck name)
|
||||
ShadowBackgroundLabel *topRightLabel; ///< Overlay label at top-right (card count)
|
||||
ShadowBackgroundLabel *bottomLeftLabel; ///< Overlay label at bottom-left (EDH bracket)
|
||||
ShadowBackgroundLabel *bottomRightLabel; ///< Overlay label at bottom-right (views)
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Handles resize events to reposition the image and overlay labels.
|
||||
* @param event Resize event.
|
||||
*/
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H
|
||||
@@ -0,0 +1,562 @@
|
||||
#include "tab_archidekt.h"
|
||||
|
||||
#include "../../../../../client/settings/cache_settings.h"
|
||||
#include "../../../cards/additional_info/mana_symbol_widget.h"
|
||||
#include "../../tab_supervisor.h"
|
||||
#include "api_response/archidekt_deck_listing_api_response.h"
|
||||
#include "display/archidekt_api_response_deck_display_widget.h"
|
||||
#include "display/archidekt_api_response_deck_listings_display_widget.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QResizeEvent>
|
||||
#include <QUrlQuery>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
|
||||
#include <libcockatrice/models/database/card/card_search_model.h>
|
||||
|
||||
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
|
||||
{
|
||||
networkManager = new QNetworkAccessManager(this);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
networkManager->setTransferTimeout(); // Use Qt's default timeout
|
||||
#endif
|
||||
|
||||
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
|
||||
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *)));
|
||||
|
||||
searchDebounceTimer = new QTimer(this);
|
||||
searchDebounceTimer->setSingleShot(true); // We only want it to fire once after inactivity
|
||||
searchDebounceTimer->setInterval(300); // 300ms debounce
|
||||
|
||||
connect(searchDebounceTimer, &QTimer::timeout, this, [this]() { doSearchImmediate(); });
|
||||
|
||||
container = new QWidget(this);
|
||||
mainLayout = new QVBoxLayout(container);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
container->setLayout(mainLayout);
|
||||
|
||||
navigationContainer = new QWidget(container);
|
||||
navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
navigationLayout = new QHBoxLayout(navigationContainer);
|
||||
navigationLayout->setSpacing(3);
|
||||
navigationContainer->setLayout(navigationLayout);
|
||||
|
||||
// Sort by
|
||||
|
||||
orderByCombo = new QComboBox(navigationContainer);
|
||||
orderByCombo->addItems({"name", "updatedAt", "createdAt", "viewCount", "size", "edhBracket"});
|
||||
orderByCombo->setCurrentText("updatedAt"); // Pre-select updatedAt
|
||||
|
||||
// Asc/Desc toggle
|
||||
orderDirButton = new QPushButton(tr("Desc."), navigationContainer);
|
||||
orderDirButton->setCheckable(true); // checked = DESC, unchecked = ASC
|
||||
orderDirButton->setChecked(true);
|
||||
|
||||
connect(orderByCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
|
||||
connect(orderDirButton, &QPushButton::clicked, this, [this](bool checked) {
|
||||
orderDirButton->setText(checked ? tr("Desc.") : tr("Asc."));
|
||||
doSearch();
|
||||
});
|
||||
|
||||
// Colors
|
||||
QHBoxLayout *colorLayout = new QHBoxLayout();
|
||||
QString colorIdentity = "WUBRG"; // Optionally include "C" for colorless once we have a symbol for it
|
||||
|
||||
for (const QChar &color : colorIdentity) {
|
||||
auto *manaSymbol = new ManaSymbolWidget(navigationContainer, color, false, true);
|
||||
manaSymbol->setFixedWidth(25);
|
||||
colorLayout->addWidget(manaSymbol);
|
||||
|
||||
connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, [this](QChar c, bool active) {
|
||||
if (active) {
|
||||
activeColors.insert(c);
|
||||
} else {
|
||||
activeColors.remove(c);
|
||||
}
|
||||
doSearch();
|
||||
});
|
||||
}
|
||||
|
||||
logicalAndCheck = new QCheckBox("Require ALL colors", navigationContainer);
|
||||
|
||||
// Formats
|
||||
|
||||
formatLabel = new QLabel(this);
|
||||
|
||||
formatSettingsWidget = new SettingsButtonWidget(this);
|
||||
|
||||
QStringList formatNames = {"Standard", "Modern", "Commander", "Legacy", "Vintage",
|
||||
"Pauper", "Custom", "Frontier", "Future Std", "Penny Dreadful",
|
||||
"1v1 Commander", "Dual Commander", "Brawl"};
|
||||
|
||||
for (int i = 0; i < formatNames.size(); ++i) {
|
||||
QCheckBox *formatCheckBox = new QCheckBox(formatNames[i], navigationContainer);
|
||||
connect(formatCheckBox, &QCheckBox::clicked, this, &TabArchidekt::doSearch);
|
||||
formatChecks << formatCheckBox;
|
||||
formatSettingsWidget->addSettingsWidget(formatCheckBox);
|
||||
}
|
||||
|
||||
// EDH Bracket
|
||||
edhBracketCombo = new QComboBox(navigationContainer);
|
||||
edhBracketCombo->addItem(tr("Any Bracket"));
|
||||
edhBracketCombo->addItems({"1", "2", "3", "4", "5"});
|
||||
|
||||
connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
|
||||
|
||||
// Search for Card Packages instead of Decks
|
||||
packagesCheck = new QCheckBox("Packages", navigationContainer);
|
||||
|
||||
connect(packagesCheck, &QCheckBox::clicked, this, [this]() {
|
||||
bool disable = packagesCheck->isChecked();
|
||||
for (auto *cb : formatChecks)
|
||||
cb->setEnabled(!disable);
|
||||
commandersField->setEnabled(!disable);
|
||||
deckTagNameField->setEnabled(!disable);
|
||||
edhBracketCombo->setCurrentIndex(0);
|
||||
edhBracketCombo->setEnabled(!disable);
|
||||
doSearch();
|
||||
});
|
||||
|
||||
// Deck Name
|
||||
nameField = new QLineEdit(navigationContainer);
|
||||
nameField->setPlaceholderText(tr("Deck name contains..."));
|
||||
|
||||
// Owner Name
|
||||
ownerField = new QLineEdit(navigationContainer);
|
||||
ownerField->setPlaceholderText(tr("Owner name contains..."));
|
||||
|
||||
// Contained cards
|
||||
cardsField = new QLineEdit(navigationContainer);
|
||||
cardsField->setPlaceholderText("Deck contains card...");
|
||||
|
||||
// Commanders
|
||||
commandersField = new QLineEdit(navigationContainer);
|
||||
commandersField->setPlaceholderText("Deck has commander...");
|
||||
|
||||
// DB supplemented card search
|
||||
auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
|
||||
auto displayModel = new CardDatabaseDisplayModel(this);
|
||||
displayModel->setSourceModel(cardDatabaseModel);
|
||||
auto *searchModel = new CardSearchModel(displayModel, this);
|
||||
|
||||
auto *proxyModel = new CardCompleterProxyModel(this);
|
||||
proxyModel->setSourceModel(searchModel);
|
||||
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
proxyModel->setFilterRole(Qt::DisplayRole);
|
||||
|
||||
auto *completer = new QCompleter(proxyModel, this);
|
||||
completer->setCompletionRole(Qt::DisplayRole);
|
||||
completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
completer->setFilterMode(Qt::MatchContains);
|
||||
completer->setMaxVisibleItems(10);
|
||||
|
||||
cardsField->setCompleter(completer);
|
||||
commandersField->setCompleter(completer);
|
||||
|
||||
connect(cardsField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults);
|
||||
|
||||
connect(cardsField, &QLineEdit::textChanged, this, [=](const QString &text) {
|
||||
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
|
||||
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
if (!text.isEmpty())
|
||||
completer->complete();
|
||||
});
|
||||
|
||||
connect(commandersField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults);
|
||||
|
||||
connect(commandersField, &QLineEdit::textChanged, this, [=](const QString &text) {
|
||||
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
|
||||
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
if (!text.isEmpty())
|
||||
completer->complete();
|
||||
});
|
||||
|
||||
// Tag Name
|
||||
deckTagNameField = new QLineEdit(navigationContainer);
|
||||
deckTagNameField->setPlaceholderText("Deck tag");
|
||||
|
||||
connect(deckTagNameField, &QLineEdit::textChanged, this, &TabArchidekt::doSearch);
|
||||
|
||||
// Search button
|
||||
searchPushButton = new QPushButton(navigationContainer);
|
||||
searchPushButton->setText("Search");
|
||||
|
||||
connect(searchPushButton, &QPushButton::clicked, this, &TabArchidekt::doSearch);
|
||||
|
||||
// Card Size settings
|
||||
settingsButton = new SettingsButtonWidget(this);
|
||||
cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().getArchidektPreviewSize());
|
||||
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
|
||||
&SettingsCache::setArchidektPreviewCardSize);
|
||||
settingsButton->addSettingsWidget(cardSizeSlider);
|
||||
|
||||
// Min deck size
|
||||
minDeckSizeLabel = new QLabel(navigationContainer);
|
||||
|
||||
minDeckSizeSpin = new QSpinBox(navigationContainer);
|
||||
minDeckSizeSpin->setSpecialValueText(tr("Disabled"));
|
||||
minDeckSizeSpin->setRange(0, 200);
|
||||
minDeckSizeSpin->setValue(0);
|
||||
|
||||
// Size logic
|
||||
minDeckSizeLogicCombo = new QComboBox(navigationContainer);
|
||||
minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); // Exact = unset, ≥ = GTE, ≤ = LTE
|
||||
minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE
|
||||
|
||||
connect(minDeckSizeSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
|
||||
connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
|
||||
|
||||
// Page number
|
||||
pageLabel = new QLabel(navigationContainer);
|
||||
|
||||
pageSpin = new QSpinBox(navigationContainer);
|
||||
pageSpin->setRange(1, 9999);
|
||||
pageSpin->setValue(1);
|
||||
|
||||
connect(pageSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
|
||||
|
||||
// Page display
|
||||
currentPageDisplay = new QWidget(container);
|
||||
currentPageLayout = new QVBoxLayout(currentPageDisplay);
|
||||
currentPageLayout->setContentsMargins(0, 0, 0, 0);
|
||||
currentPageDisplay->setLayout(currentPageLayout);
|
||||
|
||||
// Layout composition
|
||||
|
||||
// Sort section
|
||||
navigationLayout->addWidget(orderByCombo);
|
||||
navigationLayout->addWidget(orderDirButton);
|
||||
|
||||
// Colors section
|
||||
navigationLayout->addLayout(colorLayout);
|
||||
navigationLayout->addWidget(logicalAndCheck);
|
||||
|
||||
// Formats section
|
||||
navigationLayout->addWidget(formatSettingsWidget);
|
||||
navigationLayout->addWidget(formatLabel);
|
||||
|
||||
// EDH Bracket
|
||||
navigationLayout->addWidget(edhBracketCombo);
|
||||
|
||||
// Packages toggle
|
||||
navigationLayout->addWidget(packagesCheck);
|
||||
|
||||
// Deck name
|
||||
navigationLayout->addWidget(nameField);
|
||||
|
||||
// Owner name
|
||||
navigationLayout->addWidget(ownerField);
|
||||
|
||||
// Contained cards
|
||||
navigationLayout->addWidget(cardsField);
|
||||
|
||||
// Commanders
|
||||
navigationLayout->addWidget(commandersField);
|
||||
|
||||
// Deck tag
|
||||
navigationLayout->addWidget(deckTagNameField);
|
||||
|
||||
// Search button
|
||||
navigationLayout->addWidget(searchPushButton);
|
||||
|
||||
// Card size settings
|
||||
navigationLayout->addWidget(settingsButton);
|
||||
|
||||
// Min. # of cards in deck
|
||||
navigationLayout->addWidget(minDeckSizeLabel);
|
||||
navigationLayout->addWidget(minDeckSizeSpin);
|
||||
navigationLayout->addWidget(minDeckSizeLogicCombo);
|
||||
|
||||
// Page number
|
||||
navigationLayout->addWidget(pageLabel);
|
||||
navigationLayout->addWidget(pageSpin);
|
||||
|
||||
mainLayout->addWidget(navigationContainer);
|
||||
mainLayout->addWidget(currentPageDisplay);
|
||||
|
||||
// Ensure navigation stays at the top and currentPageDisplay takes remaining space
|
||||
mainLayout->setStretch(0, 0); // navigationContainer gets minimum space
|
||||
mainLayout->setStretch(1, 1); // currentPageDisplay expands as much as possible
|
||||
|
||||
setCentralWidget(container);
|
||||
|
||||
TabArchidekt::retranslateUi();
|
||||
|
||||
getTopDecks();
|
||||
}
|
||||
|
||||
void TabArchidekt::retranslateUi()
|
||||
{
|
||||
searchPushButton->setText(tr("Search"));
|
||||
formatLabel->setText(tr("Formats"));
|
||||
minDeckSizeLabel->setText(tr("Min. # of Cards:"));
|
||||
pageLabel->setText(tr("Page:"));
|
||||
}
|
||||
|
||||
QString TabArchidekt::buildSearchUrl()
|
||||
{
|
||||
QUrlQuery query;
|
||||
|
||||
// orderBy (field + direction)
|
||||
{
|
||||
QString field = orderByCombo->currentText();
|
||||
if (!field.isEmpty()) {
|
||||
bool desc = orderDirButton->isChecked();
|
||||
QString final = desc ? "-" + field : field;
|
||||
query.addQueryItem("orderBy", final);
|
||||
}
|
||||
}
|
||||
|
||||
// Colors
|
||||
QStringList selectedColors;
|
||||
for (QChar c : activeColors) {
|
||||
selectedColors.append(c);
|
||||
}
|
||||
if (!selectedColors.isEmpty()) {
|
||||
query.addQueryItem("colors", selectedColors.join(","));
|
||||
}
|
||||
|
||||
// logicalAnd
|
||||
if (logicalAndCheck->isChecked()) {
|
||||
query.addQueryItem("logicalAnd", "true");
|
||||
}
|
||||
|
||||
// Formats
|
||||
if (!packagesCheck->isChecked()) {
|
||||
QStringList formatIds;
|
||||
for (int i = 0; i < formatChecks.size(); ++i)
|
||||
if (formatChecks[i]->isChecked()) {
|
||||
formatIds << QString::number(i + 1);
|
||||
}
|
||||
|
||||
if (!formatIds.isEmpty()) {
|
||||
query.addQueryItem("deckFormat", formatIds.join(","));
|
||||
}
|
||||
}
|
||||
|
||||
// edhBracket
|
||||
if (!packagesCheck->isChecked()) {
|
||||
if (!edhBracketCombo->currentText().isEmpty()) {
|
||||
if (edhBracketCombo->currentText() != tr("Any Bracket")) {
|
||||
query.addQueryItem("edhBracket", edhBracketCombo->currentText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for card packages instead of decks
|
||||
if (packagesCheck->isChecked()) {
|
||||
query.addQueryItem("packages", "true");
|
||||
}
|
||||
|
||||
// Name
|
||||
if (!nameField->text().isEmpty()) {
|
||||
query.addQueryItem("name", nameField->text());
|
||||
}
|
||||
|
||||
// owner
|
||||
if (!ownerField->text().isEmpty()) {
|
||||
query.addQueryItem("ownerUsername", ownerField->text());
|
||||
}
|
||||
|
||||
// cards
|
||||
if (!cardsField->text().isEmpty()) {
|
||||
query.addQueryItem("cardName", cardsField->text());
|
||||
}
|
||||
|
||||
// Commander Name
|
||||
if (!packagesCheck->isChecked()) {
|
||||
if (!commandersField->text().isEmpty()) {
|
||||
query.addQueryItem("commanderName", commandersField->text());
|
||||
}
|
||||
}
|
||||
|
||||
// deckTagName
|
||||
if (!packagesCheck->isChecked()) {
|
||||
if (!deckTagNameField->text().isEmpty()) {
|
||||
query.addQueryItem("deckTagName", deckTagNameField->text());
|
||||
}
|
||||
}
|
||||
|
||||
// page number
|
||||
if (pageSpin->value() <= 1) {
|
||||
query.addQueryItem("page", QString::number(pageSpin->value()));
|
||||
}
|
||||
|
||||
// Min deck size
|
||||
if (minDeckSizeSpin->value() != 0) {
|
||||
query.addQueryItem("size", QString::number(minDeckSizeSpin->value()));
|
||||
|
||||
QString logic = "GTE"; // default
|
||||
QString selected = minDeckSizeLogicCombo->currentText();
|
||||
if (selected == "≥")
|
||||
logic = "GTE";
|
||||
else if (selected == "≤")
|
||||
logic = "LTE";
|
||||
else
|
||||
logic = ""; // Exact = unset
|
||||
|
||||
if (!logic.isEmpty()) {
|
||||
query.addQueryItem("sizeLogic", logic);
|
||||
}
|
||||
}
|
||||
|
||||
// build final URL
|
||||
QUrl url("https://archidekt.com/api/decks/v3/");
|
||||
url.setQuery(query);
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
void TabArchidekt::doSearch()
|
||||
{
|
||||
searchDebounceTimer->start();
|
||||
}
|
||||
|
||||
void TabArchidekt::doSearchImmediate()
|
||||
{
|
||||
QString url = buildSearchUrl();
|
||||
QNetworkRequest req{QUrl(url)};
|
||||
networkManager->get(req);
|
||||
}
|
||||
|
||||
void TabArchidekt::actNavigatePage(QString url)
|
||||
{
|
||||
QNetworkRequest request{QUrl(url)};
|
||||
networkManager->get(request);
|
||||
}
|
||||
|
||||
void TabArchidekt::getTopDecks()
|
||||
{
|
||||
QNetworkRequest request{QUrl(buildSearchUrl())};
|
||||
networkManager->get(request);
|
||||
}
|
||||
|
||||
void TabArchidekt::processApiJson(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qDebug() << "Network error occurred:" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
|
||||
|
||||
if (!jsonDoc.isObject()) {
|
||||
qDebug() << "Invalid JSON response received.";
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
|
||||
// Get the actual URL from the reply
|
||||
QString responseUrl = reply->url().toString();
|
||||
|
||||
// Check if the response URL matches a commander request
|
||||
if (responseUrl.startsWith("https://archidekt.com/api/decks/v3/")) {
|
||||
processTopDecksResponse(jsonObj);
|
||||
} else if (responseUrl.startsWith("https://archidekt.com/api/decks/")) {
|
||||
processDeckResponse(jsonObj);
|
||||
} else {
|
||||
prettyPrintJson(jsonObj, 4);
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void TabArchidekt::processTopDecksResponse(QJsonObject reply)
|
||||
{
|
||||
ArchidektDeckListingApiResponse deckData;
|
||||
deckData.fromJson(reply);
|
||||
|
||||
// **Remove previous page display to prevent stacking**
|
||||
if (currentPageDisplay) {
|
||||
mainLayout->removeWidget(currentPageDisplay);
|
||||
delete currentPageDisplay;
|
||||
currentPageDisplay = nullptr;
|
||||
}
|
||||
|
||||
// **Create new currentPageDisplay**
|
||||
currentPageDisplay = new QWidget(container);
|
||||
currentPageLayout = new QVBoxLayout(currentPageDisplay);
|
||||
currentPageDisplay->setLayout(currentPageLayout);
|
||||
|
||||
auto display = new ArchidektApiResponseDeckListingsDisplayWidget(currentPageDisplay, deckData, cardSizeSlider);
|
||||
connect(display, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation, this,
|
||||
&TabArchidekt::actNavigatePage);
|
||||
currentPageLayout->addWidget(display);
|
||||
|
||||
mainLayout->addWidget(currentPageDisplay);
|
||||
|
||||
// **Ensure layout stays correct**
|
||||
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
|
||||
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
|
||||
}
|
||||
|
||||
void TabArchidekt::processDeckResponse(QJsonObject reply)
|
||||
{
|
||||
ArchidektApiResponseDeck deckData;
|
||||
deckData.fromJson(reply);
|
||||
|
||||
// **Remove previous page display to prevent stacking**
|
||||
if (currentPageDisplay) {
|
||||
mainLayout->removeWidget(currentPageDisplay);
|
||||
delete currentPageDisplay;
|
||||
currentPageDisplay = nullptr;
|
||||
}
|
||||
|
||||
// **Create new currentPageDisplay**
|
||||
currentPageDisplay = new QWidget(container);
|
||||
currentPageLayout = new QVBoxLayout(currentPageDisplay);
|
||||
currentPageDisplay->setLayout(currentPageLayout);
|
||||
|
||||
auto display = new ArchidektApiResponseDeckDisplayWidget(currentPageDisplay, deckData, cardSizeSlider);
|
||||
connect(display, &ArchidektApiResponseDeckDisplayWidget::requestNavigation, this, &TabArchidekt::actNavigatePage);
|
||||
connect(display, &ArchidektApiResponseDeckDisplayWidget::openInDeckEditor, tabSupervisor,
|
||||
&TabSupervisor::openDeckInNewTab);
|
||||
currentPageLayout->addWidget(display);
|
||||
|
||||
mainLayout->addWidget(currentPageDisplay);
|
||||
|
||||
// **Ensure layout stays correct**
|
||||
mainLayout->setStretch(0, 0); // Keep navigationContainer at the top
|
||||
mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space
|
||||
}
|
||||
|
||||
void TabArchidekt::prettyPrintJson(const QJsonValue &value, int indentLevel)
|
||||
{
|
||||
const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing
|
||||
|
||||
if (value.isObject()) {
|
||||
QJsonObject obj = value.toObject();
|
||||
for (auto it = obj.begin(); it != obj.end(); ++it) {
|
||||
qDebug().noquote() << indent + it.key() + ":";
|
||||
prettyPrintJson(it.value(), indentLevel + 1);
|
||||
}
|
||||
} else if (value.isArray()) {
|
||||
QJsonArray array = value.toArray();
|
||||
for (int i = 0; i < array.size(); ++i) {
|
||||
qDebug().noquote() << indent + QString("[%1]:").arg(i);
|
||||
prettyPrintJson(array[i], indentLevel + 1);
|
||||
}
|
||||
} else if (value.isString()) {
|
||||
qDebug().noquote() << indent + "\"" + value.toString() + "\"";
|
||||
} else if (value.isDouble()) {
|
||||
qDebug().noquote() << indent + QString::number(value.toDouble());
|
||||
} else if (value.isBool()) {
|
||||
qDebug().noquote() << indent + (value.toBool() ? "true" : "false");
|
||||
} else if (value.isNull()) {
|
||||
qDebug().noquote() << indent + "null";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
#ifndef COCKATRICE_TAB_ARCHIDEKT_H
|
||||
#define COCKATRICE_TAB_ARCHIDEKT_H
|
||||
|
||||
#include "../../interface/widgets/cards/card_size_widget.h"
|
||||
#include "../../interface/widgets/quick_settings/settings_button_widget.h"
|
||||
#include "../../tab.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QString>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
|
||||
/** Base API link for Archidekt deck search */
|
||||
inline QString archidektApiLink = "https://archidekt.com/api/decks/v3/?name=";
|
||||
|
||||
/**
|
||||
* @brief Tab for browsing, searching, and filtering Archidekt decks.
|
||||
*
|
||||
* This class provides a comprehensive interface for querying decks from the Archidekt API.
|
||||
* Users can filter decks by name, owner, included cards, commanders, deck tags, colors, EDH bracket,
|
||||
* and formats. It also provides sorting and pagination, as well as a card size adjustment widget.
|
||||
*/
|
||||
class TabArchidekt : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new TabArchidekt object
|
||||
* @param _tabSupervisor Parent tab supervisor responsible for tab management and callbacks
|
||||
*
|
||||
* Initializes the network manager, creates all UI components, sets up layouts,
|
||||
* connects signals and slots, and triggers an initial fetch of top decks.
|
||||
*/
|
||||
explicit TabArchidekt(TabSupervisor *_tabSupervisor);
|
||||
|
||||
/**
|
||||
* @brief Update all UI text to reflect the current language or translation
|
||||
*
|
||||
* This function re-applies translations to all labels, buttons, and placeholders.
|
||||
* It should be called after a language change.
|
||||
*/
|
||||
void retranslateUi() override;
|
||||
|
||||
/**
|
||||
* @brief Construct the search URL from all current filters
|
||||
* @return QString Fully constructed URL including all query parameters
|
||||
*
|
||||
* The search URL is dynamically built using the state of all filter widgets.
|
||||
* Parameters included:
|
||||
* - Deck name
|
||||
* - Owner
|
||||
* - Included cards
|
||||
* - Commander cards
|
||||
* - Deck tag
|
||||
* - Colors and logical AND requirement
|
||||
* - Formats
|
||||
* - EDH bracket
|
||||
* - Packages toggle
|
||||
* - Sorting field and direction
|
||||
* - Minimum amount of cards in the deck
|
||||
* - Pagination (page)
|
||||
*/
|
||||
QString buildSearchUrl();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the tab display text
|
||||
* @return QString Human-readable title for the tab
|
||||
*
|
||||
* If a card is pre-selected (cardToQuery), its name is appended to the tab title.
|
||||
*/
|
||||
QString getTabText() const override
|
||||
{
|
||||
auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName();
|
||||
return tr("Archidekt: ") + cardName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the card size slider widget
|
||||
* @return CardSizeWidget* Pointer to the card size adjustment slider
|
||||
*
|
||||
* Allows external code to read or manipulate the current card size or hook up the sliders signals.
|
||||
*/
|
||||
CardSizeWidget *getCardSizeSlider()
|
||||
{
|
||||
return cardSizeSlider;
|
||||
}
|
||||
|
||||
/** @brief Network manager for handling API requests */
|
||||
QNetworkAccessManager *networkManager;
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Trigger a search using the current filters
|
||||
*
|
||||
* Sends a network request to the Archidekt API using the URL generated by buildSearchUrl().
|
||||
* Updates the current page display with results asynchronously.
|
||||
*/
|
||||
void doSearch();
|
||||
void doSearchImmediate();
|
||||
/**
|
||||
* @brief Process a network reply containing JSON data
|
||||
* @param reply QNetworkReply object with the API response
|
||||
*
|
||||
* Determines whether the response corresponds to a top decks query or a single deck,
|
||||
* and dispatches it to the appropriate handler.
|
||||
*/
|
||||
void processApiJson(QNetworkReply *reply);
|
||||
|
||||
/**
|
||||
* @brief Handle a JSON response containing multiple decks
|
||||
* @param reply QJsonObject containing top deck listings
|
||||
*
|
||||
* Clears the previous page display and creates a new display widget for the results.
|
||||
*/
|
||||
void processTopDecksResponse(QJsonObject reply);
|
||||
|
||||
/**
|
||||
* @brief Handle a JSON response for a single deck
|
||||
* @param reply QJsonObject containing deck data
|
||||
*
|
||||
* Clears the previous page display and creates a new display widget for the deck details.
|
||||
*/
|
||||
void processDeckResponse(QJsonObject reply);
|
||||
|
||||
/**
|
||||
* @brief Pretty-print a QJsonValue for debugging
|
||||
* @param value The JSON value to print
|
||||
* @param indentLevel The indentation depth (number of levels)
|
||||
*/
|
||||
void prettyPrintJson(const QJsonValue &value, int indentLevel);
|
||||
|
||||
/**
|
||||
* @brief Navigate to a specified page URL
|
||||
* @param url The URL to request
|
||||
*
|
||||
* Typically called when a navigation button is clicked in a deck listing.
|
||||
*/
|
||||
void actNavigatePage(QString url);
|
||||
|
||||
/**
|
||||
* @brief Fetch top decks from the Archidekt API
|
||||
*
|
||||
* Called on initialization to populate the initial page display.
|
||||
*/
|
||||
void getTopDecks();
|
||||
|
||||
private:
|
||||
QTimer *searchDebounceTimer; ///< Timer to debounce search requests by spin-boxes etc.
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Layout Containers
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QWidget *container; ///< Root container for the entire tab
|
||||
QVBoxLayout *mainLayout; ///< Outer vertical layout containing navigation and page display
|
||||
QWidget *navigationContainer; ///< Container for all navigation/filter controls
|
||||
QHBoxLayout *navigationLayout; ///< Layout for horizontal arrangement of filter widgets
|
||||
QWidget *currentPageDisplay; ///< Widget containing the currently displayed deck(s)
|
||||
QVBoxLayout *currentPageLayout; ///< Layout for deck display widgets
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Sorting Controls
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QComboBox *orderByCombo; ///< Dropdown for selecting the sort field
|
||||
QPushButton *orderDirButton; ///< Toggle button for ascending/descending sort
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Color Filters
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QSet<QChar> activeColors; ///< Set of currently active mana colors
|
||||
QCheckBox *logicalAndCheck; ///< Require ALL selected colors instead of ANY
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Format Filters
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QLabel *formatLabel; ///< Label displaying "Formats"
|
||||
SettingsButtonWidget *formatSettingsWidget; ///< Collapsible widget containing format checkboxes
|
||||
QVector<QCheckBox *> formatChecks; ///< Individual checkboxes for each format
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// EDH Bracket / Package Toggle
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection
|
||||
QCheckBox *packagesCheck; ///< Toggle for searching card packages instead of full decks
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Basic Search Fields
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QLineEdit *nameField; ///< Input for deck name filter
|
||||
QLineEdit *ownerField; ///< Input for owner name filter
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Card Filters
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QLineEdit *cardsField; ///< Input for cards included in the deck (comma-separated)
|
||||
QLineEdit *commandersField; ///< Input for commander cards (comma-separated)
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Deck Tag
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QLineEdit *deckTagNameField; ///< Input for deck tag filtering
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Search Trigger
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QPushButton *searchPushButton; ///< Button to trigger the search manually
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// UI Settings (Card Size)
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
SettingsButtonWidget *settingsButton; ///< Container for additional UI settings
|
||||
CardSizeWidget *cardSizeSlider; ///< Slider to adjust card size in results
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Minimum Cards in Deck
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QLabel *minDeckSizeLabel; ///< Label for minimum number of cards per deck
|
||||
QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size
|
||||
QComboBox *minDeckSizeLogicCombo; ///< Combo box for the size logic to apply
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Pagination
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
QLabel *pageLabel; ///< Label for current page selection
|
||||
QSpinBox *pageSpin; ///< Spinner to select the page number for results
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Optional Context
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
CardInfoPtr cardToQuery; ///< Optional pre-selected card for initial filtering
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TAB_ARCHIDEKT_H
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../interface/widgets/server/user/user_list_manager.h"
|
||||
#include "../interface/widgets/server/user/user_list_widget.h"
|
||||
#include "../main.h"
|
||||
#include "api/archidekt/tab_archidekt.h"
|
||||
#include "api/edhrec/tab_edhrec_main.h"
|
||||
#include "tab_account.h"
|
||||
#include "tab_admin.h"
|
||||
@@ -140,6 +141,9 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
|
||||
aTabEdhRec = new QAction(this);
|
||||
connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); });
|
||||
|
||||
aTabArchidekt = new QAction(this);
|
||||
connect(aTabArchidekt, &QAction::triggered, this, [this] { addArchidektTab(); });
|
||||
|
||||
aTabHome = new QAction(this);
|
||||
aTabHome->setCheckable(true);
|
||||
connect(aTabHome, &QAction::triggered, this, &TabSupervisor::actTabHome);
|
||||
@@ -204,6 +208,7 @@ void TabSupervisor::retranslateUi()
|
||||
aTabDeckEditor->setText(tr("Deck Editor"));
|
||||
aTabVisualDeckEditor->setText(tr("Visual Deck Editor"));
|
||||
aTabEdhRec->setText(tr("EDHRec"));
|
||||
aTabArchidekt->setText(tr("Archidekt"));
|
||||
aTabHome->setText(tr("Home"));
|
||||
aTabVisualDeckStorage->setText(tr("&Visual Deck Storage"));
|
||||
aTabVisualDatabaseDisplay->setText(tr("Visual Database Display"));
|
||||
@@ -386,6 +391,7 @@ void TabSupervisor::resetTabsMenu()
|
||||
tabsMenu->addAction(aTabDeckEditor);
|
||||
tabsMenu->addAction(aTabVisualDeckEditor);
|
||||
tabsMenu->addAction(aTabEdhRec);
|
||||
tabsMenu->addAction(aTabArchidekt);
|
||||
tabsMenu->addSeparator();
|
||||
tabsMenu->addAction(aTabHome);
|
||||
tabsMenu->addAction(aTabVisualDeckStorage);
|
||||
@@ -899,6 +905,15 @@ TabEdhRecMain *TabSupervisor::addEdhrecMainTab()
|
||||
return tab;
|
||||
}
|
||||
|
||||
TabArchidekt *TabSupervisor::addArchidektTab()
|
||||
{
|
||||
auto *tab = new TabArchidekt(this);
|
||||
|
||||
myAddTab(tab);
|
||||
setCurrentWidget(tab);
|
||||
return tab;
|
||||
}
|
||||
|
||||
TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab()
|
||||
{
|
||||
auto *tab = new TabVisualDatabaseDisplay(this);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "../../deck_loader/deck_loader.h"
|
||||
#include "../interface/widgets/server/user/user_list_proxy.h"
|
||||
#include "abstract_tab_deck_editor.h"
|
||||
#include "api/archidekt/tab_archidekt.h"
|
||||
#include "api/edhrec/tab_edhrec.h"
|
||||
#include "api/edhrec/tab_edhrec_main.h"
|
||||
#include "tab_visual_database_display.h"
|
||||
@@ -110,7 +111,7 @@ private:
|
||||
QList<AbstractTabDeckEditor *> deckEditorTabs;
|
||||
bool isLocalGame;
|
||||
|
||||
QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabVisualDeckStorage,
|
||||
QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage,
|
||||
*aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog;
|
||||
|
||||
int myAddTab(Tab *tab, QAction *manager = nullptr);
|
||||
@@ -172,6 +173,7 @@ public slots:
|
||||
TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen);
|
||||
TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab();
|
||||
TabEdhRecMain *addEdhrecMainTab();
|
||||
TabArchidekt *addArchidektTab();
|
||||
TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
|
||||
void openReplay(GameReplay *replay);
|
||||
void switchToFirstAvailableNetworkTab();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 QWidget—may 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 QListWidget’s 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -74,6 +74,29 @@ enum Type
|
||||
MANA_COST, /**< Group cards by their total mana cost. */
|
||||
COLOR /**< Group cards by their color identity. */
|
||||
};
|
||||
static inline QString toString(Type t)
|
||||
{
|
||||
switch (t) {
|
||||
case MAIN_TYPE:
|
||||
return "Main Type";
|
||||
case MANA_COST:
|
||||
return "Mana Cost";
|
||||
case COLOR:
|
||||
return "Colors";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static inline Type fromString(const QString &s)
|
||||
{
|
||||
if (s == "Main Type")
|
||||
return MAIN_TYPE;
|
||||
if (s == "Mana Cost")
|
||||
return MANA_COST;
|
||||
if (s == "Colors")
|
||||
return COLOR;
|
||||
return MAIN_TYPE; // default
|
||||
}
|
||||
} // namespace DeckListModelGroupCriteria
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user