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_editor_printing_selector_dock_widget.cpp
|
||||||
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
|
||||||
src/interface/widgets/general/background_sources.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/banner_widget.cpp
|
||||||
src/interface/widgets/general/display/bar_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_label.cpp
|
||||||
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
|
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||||
src/interface/widgets/general/display/labeled_input.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_sub_type_filter_widget.cpp
|
||||||
src/interface/widgets/visual_database_display/visual_database_display_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_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_sample_hand_widget.cpp
|
||||||
src/interface/widgets/visual_deck_editor/visual_deck_editor_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
|
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/interface/window_main.cpp
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/interface/widgets/tabs/abstract_tab_deck_editor.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/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_average_deck_api_response.cpp
|
||||||
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_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();
|
CardGroupDisplayWidget::updateCardDisplays();
|
||||||
|
|
||||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition);
|
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);
|
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +182,9 @@ void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSort
|
|||||||
void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event)
|
void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::mousePressEvent(event);
|
QWidget::mousePressEvent(event);
|
||||||
selectionModel->clearSelection();
|
if (selectionModel) {
|
||||||
|
selectionModel->clearSelection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
|
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
|
|||||||
displayCards();
|
displayCards();
|
||||||
|
|
||||||
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition);
|
connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition);
|
||||||
connect(selectionModel, &QItemSelectionModel::selectionChanged, this,
|
if (selectionModel) {
|
||||||
&DeckCardZoneDisplayWidget::onSelectionChanged);
|
connect(selectionModel, &QItemSelectionModel::selectionChanged, this,
|
||||||
|
&DeckCardZoneDisplayWidget::onSelectionChanged);
|
||||||
|
}
|
||||||
connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval);
|
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
|
||||||
@@ -12,12 +12,18 @@
|
|||||||
*/
|
*/
|
||||||
ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent)
|
ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_TranslucentBackground); // Allows transparency.
|
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.
|
setAlignment(Qt::AlignCenter); ///< Centers the text within the label.
|
||||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ///< Ensures minimum size constraints.
|
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.
|
* @brief Handles resizing of the label.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class ShadowBackgroundLabel : public QLabel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ShadowBackgroundLabel(QWidget *parent, const QString &text);
|
explicit ShadowBackgroundLabel(QWidget *parent, const QString &text);
|
||||||
|
void setLabelText(const QString &text);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ QGroupBox *HomeWidget::createButtons()
|
|||||||
auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
||||||
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
||||||
boxLayout->addWidget(edhrecButton);
|
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);
|
auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
|
||||||
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
||||||
boxLayout->addWidget(replaybutton);
|
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_manager.h"
|
||||||
#include "../interface/widgets/server/user/user_list_widget.h"
|
#include "../interface/widgets/server/user/user_list_widget.h"
|
||||||
#include "../main.h"
|
#include "../main.h"
|
||||||
|
#include "api/archidekt/tab_archidekt.h"
|
||||||
#include "api/edhrec/tab_edhrec_main.h"
|
#include "api/edhrec/tab_edhrec_main.h"
|
||||||
#include "tab_account.h"
|
#include "tab_account.h"
|
||||||
#include "tab_admin.h"
|
#include "tab_admin.h"
|
||||||
@@ -140,6 +141,9 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
|
|||||||
aTabEdhRec = new QAction(this);
|
aTabEdhRec = new QAction(this);
|
||||||
connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); });
|
connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); });
|
||||||
|
|
||||||
|
aTabArchidekt = new QAction(this);
|
||||||
|
connect(aTabArchidekt, &QAction::triggered, this, [this] { addArchidektTab(); });
|
||||||
|
|
||||||
aTabHome = new QAction(this);
|
aTabHome = new QAction(this);
|
||||||
aTabHome->setCheckable(true);
|
aTabHome->setCheckable(true);
|
||||||
connect(aTabHome, &QAction::triggered, this, &TabSupervisor::actTabHome);
|
connect(aTabHome, &QAction::triggered, this, &TabSupervisor::actTabHome);
|
||||||
@@ -204,6 +208,7 @@ void TabSupervisor::retranslateUi()
|
|||||||
aTabDeckEditor->setText(tr("Deck Editor"));
|
aTabDeckEditor->setText(tr("Deck Editor"));
|
||||||
aTabVisualDeckEditor->setText(tr("Visual Deck Editor"));
|
aTabVisualDeckEditor->setText(tr("Visual Deck Editor"));
|
||||||
aTabEdhRec->setText(tr("EDHRec"));
|
aTabEdhRec->setText(tr("EDHRec"));
|
||||||
|
aTabArchidekt->setText(tr("Archidekt"));
|
||||||
aTabHome->setText(tr("Home"));
|
aTabHome->setText(tr("Home"));
|
||||||
aTabVisualDeckStorage->setText(tr("&Visual Deck Storage"));
|
aTabVisualDeckStorage->setText(tr("&Visual Deck Storage"));
|
||||||
aTabVisualDatabaseDisplay->setText(tr("Visual Database Display"));
|
aTabVisualDatabaseDisplay->setText(tr("Visual Database Display"));
|
||||||
@@ -386,6 +391,7 @@ void TabSupervisor::resetTabsMenu()
|
|||||||
tabsMenu->addAction(aTabDeckEditor);
|
tabsMenu->addAction(aTabDeckEditor);
|
||||||
tabsMenu->addAction(aTabVisualDeckEditor);
|
tabsMenu->addAction(aTabVisualDeckEditor);
|
||||||
tabsMenu->addAction(aTabEdhRec);
|
tabsMenu->addAction(aTabEdhRec);
|
||||||
|
tabsMenu->addAction(aTabArchidekt);
|
||||||
tabsMenu->addSeparator();
|
tabsMenu->addSeparator();
|
||||||
tabsMenu->addAction(aTabHome);
|
tabsMenu->addAction(aTabHome);
|
||||||
tabsMenu->addAction(aTabVisualDeckStorage);
|
tabsMenu->addAction(aTabVisualDeckStorage);
|
||||||
@@ -899,6 +905,15 @@ TabEdhRecMain *TabSupervisor::addEdhrecMainTab()
|
|||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TabArchidekt *TabSupervisor::addArchidektTab()
|
||||||
|
{
|
||||||
|
auto *tab = new TabArchidekt(this);
|
||||||
|
|
||||||
|
myAddTab(tab);
|
||||||
|
setCurrentWidget(tab);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab()
|
TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab()
|
||||||
{
|
{
|
||||||
auto *tab = new TabVisualDatabaseDisplay(this);
|
auto *tab = new TabVisualDatabaseDisplay(this);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "../../deck_loader/deck_loader.h"
|
#include "../../deck_loader/deck_loader.h"
|
||||||
#include "../interface/widgets/server/user/user_list_proxy.h"
|
#include "../interface/widgets/server/user/user_list_proxy.h"
|
||||||
#include "abstract_tab_deck_editor.h"
|
#include "abstract_tab_deck_editor.h"
|
||||||
|
#include "api/archidekt/tab_archidekt.h"
|
||||||
#include "api/edhrec/tab_edhrec.h"
|
#include "api/edhrec/tab_edhrec.h"
|
||||||
#include "api/edhrec/tab_edhrec_main.h"
|
#include "api/edhrec/tab_edhrec_main.h"
|
||||||
#include "tab_visual_database_display.h"
|
#include "tab_visual_database_display.h"
|
||||||
@@ -110,7 +111,7 @@ private:
|
|||||||
QList<AbstractTabDeckEditor *> deckEditorTabs;
|
QList<AbstractTabDeckEditor *> deckEditorTabs;
|
||||||
bool isLocalGame;
|
bool isLocalGame;
|
||||||
|
|
||||||
QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabVisualDeckStorage,
|
QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage,
|
||||||
*aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog;
|
*aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog;
|
||||||
|
|
||||||
int myAddTab(Tab *tab, QAction *manager = nullptr);
|
int myAddTab(Tab *tab, QAction *manager = nullptr);
|
||||||
@@ -172,6 +173,7 @@ public slots:
|
|||||||
TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen);
|
TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen);
|
||||||
TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab();
|
TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab();
|
||||||
TabEdhRecMain *addEdhrecMainTab();
|
TabEdhRecMain *addEdhrecMainTab();
|
||||||
|
TabArchidekt *addArchidektTab();
|
||||||
TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
|
TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
|
||||||
void openReplay(GameReplay *replay);
|
void openReplay(GameReplay *replay);
|
||||||
void switchToFirstAvailableNetworkTab();
|
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 "../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.h"
|
||||||
#include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h"
|
#include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h"
|
||||||
|
#include "visual_deck_display_options_widget.h"
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QCompleter>
|
#include <QCompleter>
|
||||||
@@ -109,75 +110,22 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
groupAndSortContainer = new QWidget(this);
|
displayOptionsAndSearch = new QWidget(this);
|
||||||
groupAndSortLayout = new QHBoxLayout(groupAndSortContainer);
|
displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch);
|
||||||
groupAndSortLayout->setAlignment(Qt::AlignLeft);
|
displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft);
|
||||||
groupAndSortContainer->setLayout(groupAndSortLayout);
|
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);
|
displayOptionsAndSearchLayout->addWidget(displayOptionsWidget);
|
||||||
if (auto tabWidget = qobject_cast<TabDeckEditorVisualTabWidget *>(parent)) {
|
displayOptionsAndSearchLayout->addWidget(searchBar);
|
||||||
// Inside a central widget QWidget container inside TabDeckEditorVisual
|
displayOptionsAndSearchLayout->addWidget(searchPushButton);
|
||||||
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);
|
|
||||||
|
|
||||||
scrollArea = new QScrollArea(this);
|
scrollArea = new QScrollArea(this);
|
||||||
scrollArea->setWidgetResizable(true);
|
scrollArea->setWidgetResizable(true);
|
||||||
@@ -197,7 +145,7 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
|
|||||||
connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
|
connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
|
||||||
&SettingsCache::setVisualDeckEditorCardSize);
|
&SettingsCache::setVisualDeckEditorCardSize);
|
||||||
|
|
||||||
mainLayout->addWidget(groupAndSortContainer);
|
mainLayout->addWidget(displayOptionsAndSearch);
|
||||||
mainLayout->addWidget(scrollArea);
|
mainLayout->addWidget(scrollArea);
|
||||||
mainLayout->addWidget(cardSizeWidget);
|
mainLayout->addWidget(cardSizeWidget);
|
||||||
|
|
||||||
@@ -218,17 +166,9 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
|
|||||||
void VisualDeckEditorWidget::retranslateUi()
|
void VisualDeckEditorWidget::retranslateUi()
|
||||||
{
|
{
|
||||||
searchBar->setPlaceholderText(tr("Type a card name here for suggestions from the database..."));
|
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->setText(tr("Quick search and add card"));
|
||||||
searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add "
|
searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add "
|
||||||
"preferred printing to the deck on pressing enter"));
|
"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)
|
void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model)
|
||||||
@@ -326,8 +266,9 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p
|
|||||||
{
|
{
|
||||||
DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget(
|
DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget(
|
||||||
zoneContainer, deckListModel, selectionModel, persistent,
|
zoneContainer, deckListModel, selectionModel, persistent,
|
||||||
deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), activeGroupCriteria,
|
deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(),
|
||||||
activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget);
|
displayOptionsWidget->getActiveGroupCriteria(), displayOptionsWidget->getActiveSortCriteria(),
|
||||||
|
displayOptionsWidget->getDisplayType(), 20, 10, cardSizeWidget);
|
||||||
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover);
|
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover);
|
||||||
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick);
|
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick);
|
||||||
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this,
|
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this,
|
||||||
@@ -338,7 +279,7 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p
|
|||||||
&DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged);
|
&DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged);
|
||||||
connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget,
|
connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget,
|
||||||
&DeckCardZoneDisplayWidget::refreshDisplayType);
|
&DeckCardZoneDisplayWidget::refreshDisplayType);
|
||||||
zoneDisplayWidget->refreshDisplayType(currentDisplayType);
|
zoneDisplayWidget->refreshDisplayType(displayOptionsWidget->getDisplayType());
|
||||||
zoneContainerLayout->addWidget(zoneDisplayWidget);
|
zoneContainerLayout->addWidget(zoneDisplayWidget);
|
||||||
|
|
||||||
indexToWidgetMap.insert(persistent, 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)
|
void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::resizeEvent(event);
|
QWidget::resizeEvent(event);
|
||||||
zoneContainer->setMaximumWidth(scrollArea->viewport()->width());
|
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()
|
void VisualDeckEditorWidget::decklistModelReset()
|
||||||
{
|
{
|
||||||
clearAllDisplayWidgets();
|
clearAllDisplayWidgets();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
#include <qscrollarea.h>
|
#include <qscrollarea.h>
|
||||||
|
|
||||||
|
class VisualDeckDisplayOptionsWidget;
|
||||||
class DeckCardZoneDisplayWidget;
|
class DeckCardZoneDisplayWidget;
|
||||||
enum class DisplayType
|
enum class DisplayType
|
||||||
{
|
{
|
||||||
@@ -55,7 +56,6 @@ public:
|
|||||||
public slots:
|
public slots:
|
||||||
void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight);
|
void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight);
|
||||||
void updateZoneWidgets();
|
void updateZoneWidgets();
|
||||||
void updateDisplayType();
|
|
||||||
void cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget);
|
void cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget);
|
||||||
void onCardAddition(const QModelIndex &parent, int first, int last);
|
void onCardAddition(const QModelIndex &parent, int first, int last);
|
||||||
void onCardRemoval(const QModelIndex &parent, int first, int last);
|
void onCardRemoval(const QModelIndex &parent, int first, int last);
|
||||||
@@ -73,8 +73,6 @@ signals:
|
|||||||
protected slots:
|
protected slots:
|
||||||
void onHover(const ExactCard &hoveredCard);
|
void onHover(const ExactCard &hoveredCard);
|
||||||
void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
|
void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
|
||||||
void actChangeActiveGroupCriteria();
|
|
||||||
void actChangeActiveSortCriteria();
|
|
||||||
void decklistModelReset();
|
void decklistModelReset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -85,19 +83,10 @@ private:
|
|||||||
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
|
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
|
||||||
CardCompleterProxyModel *proxyModel;
|
CardCompleterProxyModel *proxyModel;
|
||||||
QCompleter *completer;
|
QCompleter *completer;
|
||||||
|
QWidget *displayOptionsAndSearch;
|
||||||
|
QHBoxLayout *displayOptionsAndSearchLayout;
|
||||||
|
VisualDeckDisplayOptionsWidget *displayOptionsWidget;
|
||||||
QPushButton *searchPushButton;
|
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;
|
QScrollArea *scrollArea;
|
||||||
QWidget *zoneContainer;
|
QWidget *zoneContainer;
|
||||||
QVBoxLayout *zoneContainerLayout;
|
QVBoxLayout *zoneContainerLayout;
|
||||||
|
|||||||
@@ -74,6 +74,29 @@ enum Type
|
|||||||
MANA_COST, /**< Group cards by their total mana cost. */
|
MANA_COST, /**< Group cards by their total mana cost. */
|
||||||
COLOR /**< Group cards by their color identity. */
|
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
|
} // namespace DeckListModelGroupCriteria
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,8 +257,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[nodiscard]] QModelIndex findCard(const QString &cardName,
|
[[nodiscard]] QModelIndex findCard(const QString &cardName,
|
||||||
const QString &zoneName,
|
const QString &zoneName,
|
||||||
const QString &providerId = "",
|
const QString &providerId = "",
|
||||||
const QString &cardNumber = "") const;
|
const QString &cardNumber = "") const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Adds a card using the preferred printing if available.
|
* @brief Adds a card using the preferred printing if available.
|
||||||
@@ -296,8 +319,8 @@ private:
|
|||||||
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
||||||
[[nodiscard]] DecklistModelCardNode *findCardNode(const QString &cardName,
|
[[nodiscard]] DecklistModelCardNode *findCardNode(const QString &cardName,
|
||||||
const QString &zoneName,
|
const QString &zoneName,
|
||||||
const QString &providerId = "",
|
const QString &providerId = "",
|
||||||
const QString &cardNumber = "") const;
|
const QString &cardNumber = "") const;
|
||||||
void emitRecursiveUpdates(const QModelIndex &index);
|
void emitRecursiveUpdates(const QModelIndex &index);
|
||||||
void sortHelper(InnerDecklistNode *node, Qt::SortOrder order);
|
void sortHelper(InnerDecklistNode *node, Qt::SortOrder order);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user