diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 522300346..3d8bba51b 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -220,6 +220,18 @@ set(cockatrice_SOURCES src/interface/window_main.cpp src/main.cpp src/interface/widgets/tabs/abstract_tab_deck_editor.cpp + src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp + src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp @@ -303,7 +315,32 @@ if(APPLE) set_source_files_properties( ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) - set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns) + set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns + src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp + src/interface/widgets/tabs/api/archidekt/tab_archidekt.h + src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp + src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h + 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_listing_container.h + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h + 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.h + 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.h + 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_card_entry.h + 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_entry_display_widget.h + 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_display_widget.h + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h + ) endif(APPLE) if(Qt6_FOUND) diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp new file mode 100644 index 000000000..40882db04 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp @@ -0,0 +1,29 @@ +#include "archidekt_deck_listing_api_response.h" + +#include +#include + +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(); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h new file mode 100644 index 000000000..723c65ce2 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h @@ -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 +#include +#include + +class ArchidektDeckListingApiResponse +{ + +public: + int count; + QUrl next; + QVector results; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // COCKATRICE_DECK_LISTING_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp new file mode 100644 index 000000000..0086c0978 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp @@ -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; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h new file mode 100644 index 000000000..937b2f4e8 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h @@ -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 +#include +#include +#include +#include + +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 diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp new file mode 100644 index 000000000..4bf003512 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp @@ -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(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h new file mode 100644 index 000000000..f7f86e9ed --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h @@ -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 +#include +#include +#include +#include + +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 diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp new file mode 100644 index 000000000..122617a22 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp @@ -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; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h new file mode 100644 index 000000000..f58911139 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h @@ -0,0 +1,52 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H + +#include +#include +#include +#include +#include + +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 diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp new file mode 100644 index 000000000..66af90ae5 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp @@ -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(); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h new file mode 100644 index 000000000..7a4f3258c --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h @@ -0,0 +1,72 @@ +#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 +#include +#include +#include +#include + +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 getCards() const + { + return cards; + }; + + QVector 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 categories; + QStringList deckTags; + QString playgroupDeckUrl; + QString cardPackage; + QVector cards; + QStringList customCards; +}; + + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp new file mode 100644 index 000000000..990293bc6 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp @@ -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; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h new file mode 100644 index 000000000..5e218b6a4 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h @@ -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 +#include +#include +#include +#include + +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 diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp new file mode 100644 index 000000000..2f2bf9280 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp @@ -0,0 +1,57 @@ +#include "archidekt_api_response_deck_listing_container.h" + +#include "archidekt_api_response_deck_owner.h" + +#include +#include +#include + +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()); + 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; +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h new file mode 100644 index 000000000..8924bb3f5 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h @@ -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 +#include +#include +#include +#include + +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 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 colors; + QString cardPackage; + QString contest; +}; + +#endif // COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp new file mode 100644 index 000000000..cf6ef7af9 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp @@ -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; +} \ No newline at end of file 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 new file mode 100644 index 000000000..bcec0bb5f --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h @@ -0,0 +1,31 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckOwner +{ +public: + // Constructor + ArchidektApiResponseDeckOwner() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + +private: + int id; + QString userName; + QUrl avatar; + bool moderator; + int pledgeLevel; + QStringList roles; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H 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 new file mode 100644 index 000000000..ead81fcb8 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -0,0 +1,142 @@ +#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 "../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 + +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); + + 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()) { + tempDeck += QString("%1 %2 (%3) %4\n") + .arg(card.getQuantity()) + .arg(card.getCard().getOracleCard().value("name").toString()) + .arg(card.getCard().getEdition().getEditionCode()) + .arg(card.getCard().getCollectorNumber()); + } + + model = new DeckListModel(this); + connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); + model->getDeckList()->loadFromStream_Plain(deckStream, false); + + model->rebuildTree(); + + retranslateUi(); +} + +void ArchidektApiResponseDeckDisplayWidget::retranslateUi() +{ + openInEditorButton->setText(tr("Open Deck in Deck Editor")); +} + +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, persistent, + model->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), "maintype", + {"name"}, DisplayType::Overlap, 20, 10, cardSizeSlider); + /*connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, + &VisualDeckEditorWidget::cleanupInvalidZones); + connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); + connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); + connect(this, &VisualDeckEditorWidget::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(); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h new file mode 100644 index 000000000..0cd1b4479 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h @@ -0,0 +1,49 @@ +#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 "../api_response/deck/archidekt_api_response_deck.h" +#include "deck_list_model.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + void requestNavigation(QString url); + void openInDeckEditor(DeckLoader *loader); + +public: + explicit ArchidektApiResponseDeckDisplayWidget(QWidget *parent, + ArchidektApiResponseDeck response, + CardSizeWidget *cardSizeSlider); + void retranslateUi(); + void actOpenInDeckEditor(); + void clearAllDisplayWidgets(); + void decklistModelReset(); + void constructZoneWidgetsFromDeckListModel(); + void resizeEvent(QResizeEvent *event) override; + +private: + ArchidektApiResponseDeck response; + CardSizeWidget *cardSizeSlider; + QVBoxLayout *layout; + QPushButton *openInEditorButton; + QScrollArea *scrollArea; + QWidget *zoneContainer; + QVBoxLayout *zoneContainerLayout; + QWidget *container; + QHash indexToWidgetMap; + QVBoxLayout *containerLayout; + DeckListModel *model; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H 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 new file mode 100644 index 000000000..e143571b9 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -0,0 +1,34 @@ +#include "archidekt_api_response_deck_entry_display_widget.h" + +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" + +ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplayWidget( + QWidget *parent, + ArchidektApiResponseDeckListingContainer _response) + : QWidget(parent), response(_response) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + // Add widgets for deck listings + + 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); +} + +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())); +} 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 new file mode 100644 index 000000000..93c3a11e7 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h @@ -0,0 +1,34 @@ +#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 "../../../../general/layout_containers/flow_widget.h" +#include "../api_response/deck_listings/archidekt_api_response_deck_listing_container.h" + +#include +#include +#include +#include + +class ArchidektApiResponseDeckEntryDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + void requestNavigation(QString url); + +public: + explicit ArchidektApiResponseDeckEntryDisplayWidget(QWidget *parent, + ArchidektApiResponseDeckListingContainer response); + void resizeEvent(QResizeEvent *event) override; + +public slots: + void actRequestNavigationToDeck(); + +private: + QHBoxLayout *layout; + ArchidektApiResponseDeckListingContainer response; + CardInfoPictureWithTextOverlayWidget *deckPreviewDisplayWidget; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H 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 new file mode 100644 index 000000000..3b1944e4d --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -0,0 +1,34 @@ +#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) + : QWidget(parent) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + + // Add widgets for deck listings + auto deckListings = response.results; + for (const auto &deckListing : deckListings) { + auto cardListDisplayWidget = new ArchidektApiResponseDeckEntryDisplayWidget(this, deckListing); + connect(cardListDisplayWidget, &ArchidektApiResponseDeckEntryDisplayWidget::requestNavigation, this, + &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation); + flowWidget->addWidget(cardListDisplayWidget); + } + + layout->addWidget(flowWidget); +} + +void ArchidektApiResponseDeckListingsDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); +} 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 new file mode 100644 index 000000000..57052ca73 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h @@ -0,0 +1,28 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H + +#include "../../../../general/layout_containers/flow_widget.h" +#include "../api_response/archidekt_deck_listing_api_response.h" + +#include +#include +#include +#include + +class ArchidektApiResponseDeckListingsDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + void requestNavigation(QString url); + +public: + explicit ArchidektApiResponseDeckListingsDisplayWidget(QWidget *parent, ArchidektDeckListingApiResponse response); + void resizeEvent(QResizeEvent *event) override; + +private: + QHBoxLayout *layout; + FlowWidget *flowWidget; +}; + +#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 new file mode 100644 index 000000000..74e0a23b3 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -0,0 +1,258 @@ +#include "tab_archidekt.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 *))); + + container = new QWidget(this); + mainLayout = new QVBoxLayout(container); + container->setLayout(mainLayout); + + navigationContainer = new QWidget(container); + navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + navigationLayout = new QHBoxLayout(navigationContainer); + navigationLayout->setSpacing(5); + navigationContainer->setLayout(navigationLayout); + + decksPushButton = new QPushButton(navigationContainer); + connect(decksPushButton, &QPushButton::clicked, this, &TabArchidekt::getTopDecks); + + searchBar = new QLineEdit(this); + 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); + searchBar->setCompleter(completer); + + // Update suggestions dynamically + connect(searchBar, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); + connect(searchBar, &QLineEdit::textChanged, this, [=](const QString &text) { + // Ensure substring matching + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + + if (!text.isEmpty()) { + completer->complete(); // Force the dropdown to appear + } + }); + + searchPushButton = new QPushButton(navigationContainer); + connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { doSearch(); }); + + settingsButton = new SettingsButtonWidget(this); + + cardSizeSlider = new CardSizeWidget(this); + + settingsButton->addSettingsWidget(cardSizeSlider); + + navigationLayout->addWidget(decksPushButton); + navigationLayout->addWidget(searchBar); + navigationLayout->addWidget(searchPushButton); + navigationLayout->addWidget(settingsButton); + + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageDisplay->setLayout(currentPageLayout); + + 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() +{ + decksPushButton->setText(tr("&Decks")); + searchBar->setPlaceholderText(tr("Search for a card ...")); + searchPushButton->setText(tr("Search")); +} + +void TabArchidekt::doSearch() +{ + CardInfoPtr searchedCard = CardDatabaseManager::query()->getCardInfo(searchBar->text()); + if (!searchedCard) { + return; + } + + // setCard(searchedCard, canBeCommander(searchedCard)); +} + +void TabArchidekt::actNavigatePage(QString url) +{ + QNetworkRequest request{QUrl(url)}; + + networkManager->get(request); +} + +void TabArchidekt::getTopDecks() +{ + QNetworkRequest request{QUrl(archidektApiLink)}; + + 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 == "https://archidekt.com/api/decks/v3/?name=") { + 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); + 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"; + } +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h new file mode 100644 index 000000000..b6b4019f5 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h @@ -0,0 +1,63 @@ +#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 +#include +#include +#include +#include + +#include + +inline QString archidektApiLink = "https://archidekt.com/api/decks/v3/?name="; + +class TabArchidekt : public Tab +{ + Q_OBJECT +public: + explicit TabArchidekt(TabSupervisor *_tabSupervisor); + + void retranslateUi() override; + void doSearch(); + QString getTabText() const override + { + auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName(); + return tr("Archidekt: ") + cardName; + } + + CardSizeWidget *getCardSizeSlider() + { + return cardSizeSlider; + } + + QNetworkAccessManager *networkManager; + +public slots: + void processApiJson(QNetworkReply *reply); + void processTopDecksResponse(QJsonObject reply); + void processDeckResponse(QJsonObject reply); + void prettyPrintJson(const QJsonValue &value, int indentLevel); + void actNavigatePage(QString url); + void getTopDecks(); + +private: + QWidget *container; + QWidget *navigationContainer; + QWidget *currentPageDisplay; + QVBoxLayout *mainLayout; + QHBoxLayout *navigationLayout; + QVBoxLayout *currentPageLayout; + QPushButton *decksPushButton; + QLineEdit *searchBar; + QPushButton *searchPushButton; + SettingsButtonWidget *settingsButton; + CardSizeWidget *cardSizeSlider; + CardInfoPtr cardToQuery; +}; + + +#endif // COCKATRICE_TAB_ARCHIDEKT_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 18a98f5ec..df9387a6c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -5,6 +5,7 @@ #include "../interface/widgets/server/user/user_list_manager.h" #include "../interface/widgets/server/user/user_list_widget.h" #include "../main.h" +#include "api/archidekt/tab_archidekt.h" #include "api/edhrec/tab_edhrec_main.h" #include "tab_account.h" #include "tab_admin.h" @@ -140,6 +141,9 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * aTabEdhRec = new QAction(this); connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); }); + aTabArchidekt = new QAction(this); + connect(aTabArchidekt, &QAction::triggered, this, [this] { addArchidektTab(); }); + aTabHome = new QAction(this); aTabHome->setCheckable(true); connect(aTabHome, &QAction::triggered, this, &TabSupervisor::actTabHome); @@ -204,6 +208,7 @@ void TabSupervisor::retranslateUi() aTabDeckEditor->setText(tr("Deck Editor")); aTabVisualDeckEditor->setText(tr("Visual Deck Editor")); aTabEdhRec->setText(tr("EDHRec")); + aTabArchidekt->setText(tr("Archidekt")); aTabHome->setText(tr("Home")); aTabVisualDeckStorage->setText(tr("&Visual Deck Storage")); aTabVisualDatabaseDisplay->setText(tr("Visual Database Display")); @@ -386,6 +391,7 @@ void TabSupervisor::resetTabsMenu() tabsMenu->addAction(aTabDeckEditor); tabsMenu->addAction(aTabVisualDeckEditor); tabsMenu->addAction(aTabEdhRec); + tabsMenu->addAction(aTabArchidekt); tabsMenu->addSeparator(); tabsMenu->addAction(aTabHome); tabsMenu->addAction(aTabVisualDeckStorage); @@ -899,6 +905,15 @@ TabEdhRecMain *TabSupervisor::addEdhrecMainTab() return tab; } +TabArchidekt *TabSupervisor::addArchidektTab() +{ + auto *tab = new TabArchidekt(this); + + myAddTab(tab); + setCurrentWidget(tab); + return tab; +} + TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab() { auto *tab = new TabVisualDatabaseDisplay(this); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 9fd00f584..948ac6d7e 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -11,6 +11,7 @@ #include "../../deck_loader/deck_loader.h" #include "../interface/widgets/server/user/user_list_proxy.h" #include "abstract_tab_deck_editor.h" +#include "api/archidekt/tab_archidekt.h" #include "api/edhrec/tab_edhrec.h" #include "api/edhrec/tab_edhrec_main.h" #include "tab_visual_database_display.h" @@ -110,7 +111,7 @@ private: QList deckEditorTabs; bool isLocalGame; - QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabVisualDeckStorage, + QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage, *aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog; int myAddTab(Tab *tab, QAction *manager = nullptr); @@ -172,6 +173,7 @@ public slots: TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen); TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab(); TabEdhRecMain *addEdhrecMainTab(); + TabArchidekt *addArchidektTab(); TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false); void openReplay(GameReplay *replay); void switchToFirstAvailableNetworkTab();