From cbbe9e7da068d0e06bcf1d4550d1bffb38780341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCbach=2C=20Lukas?= Date: Mon, 24 Nov 2025 20:41:21 +0100 Subject: [PATCH] Search bar, fancier display, resolve providerId --- .../archidekt_api_response_deck_owner.h | 5 + ...idekt_api_response_deck_display_widget.cpp | 6 +- ...api_response_deck_entry_display_widget.cpp | 113 +++++++++++++++- ...t_api_response_deck_entry_display_widget.h | 15 ++- ..._response_deck_listings_display_widget.cpp | 9 +- ...pi_response_deck_listings_display_widget.h | 2 + .../tabs/api/archidekt/tab_archidekt.cpp | 123 ++++++++++++++++-- .../tabs/api/archidekt/tab_archidekt.h | 26 ++++ 8 files changed, 274 insertions(+), 25 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h index bcec0bb5f..970773322 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h @@ -19,6 +19,11 @@ public: // Debug method for logging void debugPrint() const; + QString getName() const + { + return userName; + } + private: int id; QString userName; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp index 8355b6270..a2692b790 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -5,9 +5,7 @@ #include "../../../../cards/card_size_widget.h" #include "../../../../cards/deck_card_zone_display_widget.h" #include "../api_response/deck/archidekt_api_response_deck.h" -#include "custom_category_grouping_deck_list_proxy_model.h" -#include "deck_list_model.h" -#include "libcockatrice/card/database/card_database_manager.h" +#include #include @@ -59,6 +57,8 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); model->getDeckList()->loadFromStream_Plain(deckStream, false); + DeckLoader::resolveSetNameAndNumberToProviderID(model->getDeckList()); + model->rebuildTree(); retranslateUi(); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index e143571b9..fd6fce6ff 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -1,23 +1,122 @@ #include "archidekt_api_response_deck_entry_display_widget.h" - #include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include +#include +#include +#include +#include +#include + ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplayWidget( QWidget *parent, - ArchidektApiResponseDeckListingContainer _response) - : QWidget(parent), response(_response) + ArchidektApiResponseDeckListingContainer _response, + QNetworkAccessManager *_imageNetworkManager) + : QWidget(parent), response(_response), imageNetworkManager(_imageNetworkManager) { - layout = new QHBoxLayout(this); + layout = new QVBoxLayout(this); setLayout(layout); - // Add widgets for deck listings + this->setMaximumWidth(200); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - deckPreviewDisplayWidget = new CardInfoPictureWithTextOverlayWidget(this); + auto headerLayout = new QVBoxLayout(); + + QLabel *deckNameLabel = new QLabel(QString("%1 (%2)").arg(response.getName()).arg(response.getSize())); + deckNameLabel->setWordWrap(true); // Allow text to wrap + deckNameLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + deckNameLabel->setMaximumWidth(200); + headerLayout->addWidget(deckNameLabel); + + // Featured image (Deck Preview Image) + picture = new QLabel(this); + picture->setText("Loading Image..."); + picture->setAlignment(Qt::AlignCenter); + picture->setMaximumWidth(200); + + QNetworkRequest req(QUrl(response.getFeatured())); + imageNetworkManager->get(req); + connect(imageNetworkManager, &QNetworkAccessManager::finished, + this, &ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished); + + headerLayout->addWidget(picture); + + QLabel *ownerLabel = new QLabel(QString("Owner: %1").arg(response.getOwner().getName())); + ownerLabel->setWordWrap(true); + ownerLabel->setMaximumWidth(200); + headerLayout->addWidget(ownerLabel); + + QLabel *edhBracketLabel = new QLabel(QString("EDH Bracket: %1").arg(response.getEDHBracket())); + edhBracketLabel->setWordWrap(true); + edhBracketLabel->setMaximumWidth(200); + headerLayout->addWidget(edhBracketLabel); + + QLabel *viewCountLabel = new QLabel(QString("Views: %1").arg(response.getViewCount())); + viewCountLabel->setWordWrap(true); + viewCountLabel->setMaximumWidth(200); + headerLayout->addWidget(viewCountLabel); + + QLabel *createdAtLabel = new QLabel(QString("Created: %1").arg(response.getCreatedAt())); + createdAtLabel->setWordWrap(true); + createdAtLabel->setMaximumWidth(200); + QLabel *updatedAtLabel = new QLabel(QString("Updated: %1").arg(response.getUpdatedAt())); + updatedAtLabel->setWordWrap(true); + updatedAtLabel->setMaximumWidth(200); + headerLayout->addWidget(createdAtLabel); + headerLayout->addWidget(updatedAtLabel); + + QLabel *colorsLabel = new QLabel("Colors: "); + QStringList colorNames; + for (auto color : response.getColors().keys()) { + colorNames << color; + } + + if (colorNames.isEmpty()) { + colorsLabel->setText("Colors: None"); + } else { + colorsLabel->setText("Colors: " + colorNames.join(", ")); + } + + colorsLabel->setWordWrap(true); + colorsLabel->setMaximumWidth(200); + headerLayout->addWidget(colorsLabel); + + layout->addLayout(headerLayout); + + /*deckPreviewDisplayWidget = new CardInfoPictureWithTextOverlayWidget(this); deckPreviewDisplayWidget->setOverlayText(QString("%1 (%2)").arg(response.getName()).arg(response.getSize())); connect(deckPreviewDisplayWidget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &ArchidektApiResponseDeckEntryDisplayWidget::actRequestNavigationToDeck); + layout->addWidget(deckPreviewDisplayWidget);*/ +} - layout->addWidget(deckPreviewDisplayWidget); +void ArchidektApiResponseDeckEntryDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + actRequestNavigationToDeck(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished(QNetworkReply *reply) +{ + if (reply->url() != response.getFeatured()) { + return; + } + + if (reply->error() != QNetworkReply::NoError) { + picture->setText("Error loading"); + reply->deleteLater(); + return; + } + + QByteArray imageData = reply->readAll(); + QPixmap pixmap; + + if (pixmap.loadFromData(imageData)) { + picture->setPixmap(pixmap.scaled(150, 150, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } else { + picture->setText("Invalid image"); + } + + reply->deleteLater(); } void ArchidektApiResponseDeckEntryDisplayWidget::resizeEvent(QResizeEvent *event) diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h index 93c3a11e7..98d26fb66 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h @@ -2,11 +2,11 @@ #define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H #include "../../../../cards/card_info_picture_with_text_overlay_widget.h" -#include "../../../../general/layout_containers/flow_widget.h" #include "../api_response/deck_listings/archidekt_api_response_deck_listing_container.h" +#include +#include #include -#include #include #include @@ -19,15 +19,22 @@ signals: public: explicit ArchidektApiResponseDeckEntryDisplayWidget(QWidget *parent, - ArchidektApiResponseDeckListingContainer response); + ArchidektApiResponseDeckListingContainer response, + QNetworkAccessManager *imageNetworkManager); + void onPreviewImageLoadFinished(QNetworkReply *reply); void resizeEvent(QResizeEvent *event) override; public slots: void actRequestNavigationToDeck(); +protected: + void mousePressEvent(QMouseEvent *event) override; + private: - QHBoxLayout *layout; + QVBoxLayout *layout; ArchidektApiResponseDeckListingContainer response; + QNetworkAccessManager *imageNetworkManager; + QLabel *picture; CardInfoPictureWithTextOverlayWidget *deckPreviewDisplayWidget; }; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp index 3b1944e4d..93556430b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -13,10 +13,17 @@ ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsD 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); + auto cardListDisplayWidget = new ArchidektApiResponseDeckEntryDisplayWidget(this, deckListing, imageNetworkManager); connect(cardListDisplayWidget, &ArchidektApiResponseDeckEntryDisplayWidget::requestNavigation, this, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation); flowWidget->addWidget(cardListDisplayWidget); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h index 57052ca73..e5062df3b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h @@ -4,6 +4,7 @@ #include "../../../../general/layout_containers/flow_widget.h" #include "../api_response/archidekt_deck_listing_api_response.h" +#include #include #include #include @@ -23,6 +24,7 @@ public: private: QHBoxLayout *layout; FlowWidget *flowWidget; + QNetworkAccessManager *imageNetworkManager; }; #endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp index 4dbb9a024..c2a11c94b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,58 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) searchPushButton = new QPushButton(navigationContainer); connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { doSearch(); }); + searchOptionsContainer = new QWidget(container); + searchOptionsLayout = new QHBoxLayout(searchOptionsContainer); + mainLayout->addWidget(searchOptionsContainer); + + nameField = new QLineEdit(searchOptionsContainer); + nameField->setPlaceholderText(tr("Deck name contains...")); + searchOptionsLayout->addWidget(nameField); + + ownerField = new QLineEdit(searchOptionsContainer); + ownerField->setPlaceholderText(tr("Owner name contains...")); + searchOptionsLayout->addWidget(ownerField); + + QHBoxLayout *colorLayout = new QHBoxLayout(); + QStringList colors = {"White", "Blue", "Black", "Green", "Red", "Colorless"}; + + for (const auto &c : colors) { + QCheckBox *chk = new QCheckBox(c, searchOptionsContainer); + colorChecks << chk; + colorLayout->addWidget(chk); + } + + searchOptionsLayout->addLayout(colorLayout); + + logicalAndCheck = new QCheckBox("Require ALL colors", searchOptionsContainer); + searchOptionsLayout->addWidget(logicalAndCheck); + + QHBoxLayout *formatLayout = new QHBoxLayout(); + 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], searchOptionsContainer); + formatChecks << formatCheckBox; + formatLayout->addWidget(formatCheckBox); + } + + searchOptionsLayout->addLayout(formatLayout); + + pageSizeSpin = new QSpinBox(searchOptionsContainer); + pageSizeSpin->setRange(1, 500); + pageSizeSpin->setValue(50); + searchOptionsLayout->addWidget(pageSizeSpin); + + cardsField = new QLineEdit(searchOptionsContainer); + cardsField->setPlaceholderText("Cards (comma separated)"); + searchOptionsLayout->addWidget(cardsField); + + commandersField = new QLineEdit(searchOptionsContainer); + commandersField->setPlaceholderText("Commanders (comma separated)"); + searchOptionsLayout->addWidget(commandersField); + settingsButton = new SettingsButtonWidget(this); cardSizeSlider = new CardSizeWidget(this); @@ -97,7 +150,8 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) // 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 + mainLayout->setStretch(1, 0); + mainLayout->setStretch(2, 1); // currentPageDisplay expands as much as possible setCentralWidget(container); @@ -113,14 +167,61 @@ void TabArchidekt::retranslateUi() searchPushButton->setText(tr("Search")); } +QString TabArchidekt::buildSearchUrl() +{ + QUrlQuery query; + + // required + query.addQueryItem("name", nameField->text()); + + // colors + QStringList selectedColors; + for (auto *chk : colorChecks) + if (chk->isChecked()) + selectedColors << chk->text(); + if (!selectedColors.isEmpty()) + query.addQueryItem("colors", selectedColors.join(",")); + + // logicalAnd + if (logicalAndCheck->isChecked()) + query.addQueryItem("logicalAnd", "true"); + + // owner + if (!ownerField->text().isEmpty()) + query.addQueryItem("owner", ownerField->text()); + + // cards + if (!cardsField->text().isEmpty()) + query.addQueryItem("cards", cardsField->text()); + + // commanders + if (!commandersField->text().isEmpty()) + query.addQueryItem("commanders", commandersField->text()); + + // formats + QStringList formatIds; + for (int i = 0; i < formatChecks.size(); ++i) + if (formatChecks[i]->isChecked()) + formatIds << QString::number(i + 1); + if (!formatIds.isEmpty()) + query.addQueryItem("formats", formatIds.join(",")); + + // page size + if (pageSizeSpin->value() != 50) + query.addQueryItem("pageSize", QString::number(pageSizeSpin->value())); + + // build final URL + QUrl url("https://archidekt.com/api/decks/v3/"); + url.setQuery(query); + + return url.toString(); +} + void TabArchidekt::doSearch() { - CardInfoPtr searchedCard = CardDatabaseManager::query()->getCardInfo(searchBar->text()); - if (!searchedCard) { - return; - } - - // setCard(searchedCard, canBeCommander(searchedCard)); + QString url = buildSearchUrl(); + QNetworkRequest req{QUrl(url)}; + networkManager->get(req); } void TabArchidekt::actNavigatePage(QString url) @@ -160,7 +261,7 @@ void TabArchidekt::processApiJson(QNetworkReply *reply) QString responseUrl = reply->url().toString(); // Check if the response URL matches a commander request - if (responseUrl == "https://archidekt.com/api/decks/v3/?name=") { + if (responseUrl.startsWith("https://archidekt.com/api/decks/v3/")) { processTopDecksResponse(jsonObj); } else if (responseUrl.startsWith("https://archidekt.com/api/decks/")) { processDeckResponse(jsonObj); @@ -197,7 +298,8 @@ void TabArchidekt::processTopDecksResponse(QJsonObject reply) // **Ensure layout stays correct** mainLayout->setStretch(0, 0); // Keep navigationContainer at the top - mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space + mainLayout->setStretch(1, 0); // Keep searchOptionsContainer at the top + mainLayout->setStretch(2, 1); // Make sure currentPageDisplay takes remaining space } void TabArchidekt::processDeckResponse(QJsonObject reply) @@ -227,7 +329,8 @@ void TabArchidekt::processDeckResponse(QJsonObject reply) // **Ensure layout stays correct** mainLayout->setStretch(0, 0); // Keep navigationContainer at the top - mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space + mainLayout->setStretch(1, 0); // Keep searchOptionsContainer at the top + mainLayout->setStretch(2, 1); // Make sure currentPageDisplay takes remaining space } void TabArchidekt::prettyPrintJson(const QJsonValue &value, int indentLevel) diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h index 38a26a00d..09b90f803 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h @@ -5,10 +5,12 @@ #include "../../interface/widgets/quick_settings/settings_button_widget.h" #include "../../tab.h" +#include #include #include #include #include +#include #include #include @@ -21,6 +23,7 @@ public: explicit TabArchidekt(TabSupervisor *_tabSupervisor); void retranslateUi() override; + QString buildSearchUrl(); void doSearch(); QString getTabText() const override { @@ -53,6 +56,29 @@ private: QPushButton *decksPushButton; QLineEdit *searchBar; QPushButton *searchPushButton; + + // --- Search options UI --- + QWidget *searchOptionsContainer; + QHBoxLayout *searchOptionsLayout; + + // Required / basic fields + QLineEdit *nameField; // Deck name substring + QLineEdit *ownerField; // Owner substring + + // Colors + QVector colorChecks; // White, Blue, Black, Green, Red, Colorless + QCheckBox *logicalAndCheck; // Require ALL colors instead of ANY + + // Formats + QVector formatChecks; // Format checkboxes (13 total) + + // Page size + QSpinBox *pageSizeSpin; + + // Cards and Commanders + QLineEdit *cardsField; // comma-separated quoted card names + QLineEdit *commandersField; // comma-separated quoted commander names + SettingsButtonWidget *settingsButton; CardSizeWidget *cardSizeSlider; CardInfoPtr cardToQuery;