diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 612181bca..b081aa46a 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -16,6 +16,7 @@ set(cockatrice_SOURCES src/client/network/replay_timeline_widget.cpp src/client/network/sets_model.cpp src/client/network/spoiler_background_updater.cpp + src/client/network/parsers/deck_link_to_api_transformer.cpp src/client/sound_engine.cpp src/client/tabs/abstract_tab_deck_editor.cpp src/client/tabs/api/edhrec/tab_edhrec.cpp @@ -165,6 +166,7 @@ set(cockatrice_SOURCES src/dialogs/dlg_forgot_password_reset.cpp src/dialogs/dlg_load_deck.cpp src/dialogs/dlg_load_deck_from_clipboard.cpp + src/dialogs/dlg_load_deck_from_website.cpp src/dialogs/dlg_load_remote_deck.cpp src/dialogs/dlg_manage_sets.cpp src/dialogs/dlg_move_top_cards_until.cpp diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index e032c2bda..d5a96f6c3 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -19,6 +19,7 @@ #tab_supervisor = true #dlg_edit_avatar = true +#dlg_load_deck_from_website = true #dlg_settings = true #dlg_tip_of_the_day = true #dlg_update = true diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp index a01ccc3cf..ae58d2878 100644 --- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp @@ -54,6 +54,9 @@ DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), d aPrintDeck = new QAction(QString(), this); connect(aPrintDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actPrintDeck); + aLoadDeckFromWebsite = new QAction(QString(), this); + connect(aLoadDeckFromWebsite, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeckFromWebsite); + aExportDeckDecklist = new QAction(QString(), this); connect(aExportDeckDecklist, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actExportDeckDecklist); @@ -97,6 +100,7 @@ DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), d addMenu(saveDeckToClipboardMenu); addSeparator(); addAction(aPrintDeck); + addAction(aLoadDeckFromWebsite); addMenu(analyzeDeckMenu); addSeparator(); addAction(deckEditor->filterDockWidget->aClearFilterOne); @@ -166,6 +170,7 @@ void DeckEditorMenu::retranslateUi() aPrintDeck->setText(tr("&Print deck...")); + aLoadDeckFromWebsite->setText(tr("Load deck from online service...")); analyzeDeckMenu->setTitle(tr("&Send deck to online service")); aExportDeckDecklist->setText(tr("Create decklist (decklist.org)")); aExportDeckDecklistXyz->setText(tr("Create decklist (decklist.xyz)")); diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h index 7bfc58c4b..7cac10bdc 100644 --- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h @@ -16,8 +16,8 @@ public: QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aEditDeckInClipboard, *aEditDeckInClipboardRaw, *aSaveDeckToClipboard, *aSaveDeckToClipboardNoSetInfo, - *aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aExportDeckDecklist, - *aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose; + *aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aLoadDeckFromWebsite, + *aExportDeckDecklist, *aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose; QMenu *loadRecentDeckMenu, *analyzeDeckMenu, *editDeckInClipboardMenu, *saveDeckToClipboardMenu; void setSaveStatus(bool newStatus); diff --git a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.cpp b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.cpp new file mode 100644 index 000000000..010b63684 --- /dev/null +++ b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.cpp @@ -0,0 +1,61 @@ +#include "deck_link_to_api_transformer.h" + +#include + +namespace DeckLinkToApiTransformer +{ + +static const QString TAPPEDOUT_BASE = "https://tappedout.net/mtg-decks/"; +static const QString TAPPEDOUT_SUFFIX = "/?fmt=txt"; + +static const QString ARCHIDEKT_BASE = "https://archidekt.com/api/decks/"; +static const QString ARCHIDEKT_SUFFIX = "/?format=json"; + +static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/"; +static const QString MOXFIELD_SUFFIX = "/"; + +static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1"; + +bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo) +{ + static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)"); + static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)"); + static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)"); + static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)"); + + QRegularExpressionMatch match; + + if ((match = rxTappedOut.match(url)).hasMatch()) { + QString slug = match.captured(1); + outInfo = ParsedDeckInfo{.baseUrl = TAPPEDOUT_BASE, + .deckID = slug, + .fullUrl = TAPPEDOUT_BASE + slug + TAPPEDOUT_SUFFIX, + .provider = DeckProvider::TappedOut}; + return true; + } else if ((match = rxArchidekt.match(url)).hasMatch()) { + QString deckID = match.captured(1); + outInfo = ParsedDeckInfo{.baseUrl = ARCHIDEKT_BASE, + .deckID = deckID, + .fullUrl = ARCHIDEKT_BASE + deckID + ARCHIDEKT_SUFFIX, + .provider = DeckProvider::Archidekt}; + return true; + } else if ((match = rxMoxfield.match(url)).hasMatch()) { + QString deckID = match.captured(1); + outInfo = ParsedDeckInfo{.baseUrl = MOXFIELD_BASE, + .deckID = deckID, + .fullUrl = MOXFIELD_BASE + deckID + MOXFIELD_SUFFIX, + .provider = DeckProvider::Moxfield}; + return true; + } else if ((match = rxDeckstats.match(url)).hasMatch()) { + QString deckPath = match.captured(1); + outInfo = ParsedDeckInfo{.baseUrl = "https://deckstats.net/decks/", + .deckID = deckPath, + .fullUrl = "https://deckstats.net/decks/" + deckPath + DECKSTATS_SUFFIX, + .provider = DeckProvider::Deckstats}; + return true; + } + + return false; +} + +} // namespace DeckLinkToApiTransformer diff --git a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h new file mode 100644 index 000000000..dd1f4ac35 --- /dev/null +++ b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h @@ -0,0 +1,31 @@ +#ifndef DECK_LINK_TO_API_TRANSFORMER_H +#define DECK_LINK_TO_API_TRANSFORMER_H + +#include + +enum class DeckProvider +{ + TappedOut, + Archidekt, + Moxfield, + Deckstats, + Unknown +}; + +struct ParsedDeckInfo +{ + QString baseUrl; + QString deckID; + QString fullUrl; + DeckProvider provider; +}; + +namespace DeckLinkToApiTransformer +{ + +// Returns true if the input URL is recognized and fills outInfo. +bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo); + +} // namespace DeckLinkToApiTransformer + +#endif // DECK_LINK_TO_API_TRANSFORMER_H diff --git a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h new file mode 100644 index 000000000..77d76c643 --- /dev/null +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -0,0 +1,112 @@ +#ifndef INTERFACE_JSON_DECK_PARSER_H +#define INTERFACE_JSON_DECK_PARSER_H +#include "../../../deck/deck_loader.h" + +#include +#include + +class IJsonDeckParser +{ +public: + virtual ~IJsonDeckParser() = default; + + virtual DeckLoader *parse(const QJsonObject &obj) = 0; +}; + +class ArchidektJsonParser : public IJsonDeckParser +{ +public: + DeckLoader *parse(const QJsonObject &obj) override + { + DeckLoader *list = new DeckLoader(); + + QString deckName = obj.value("name").toString(); + QString deckDescription = obj.value("description").toString(); + + list->setName(deckName); + list->setComments(deckDescription); + + QString outputText; + QTextStream outStream(&outputText); + + for (auto entry : obj.value("cards").toArray()) { + auto quantity = entry.toObject().value("quantity").toInt(); + + auto card = entry.toObject().value("card").toObject(); + auto oracleCard = card.value("oracleCard").toObject(); + QString cardName = oracleCard.value("name").toString(); + QString setName = card.value("edition").toObject().value("editioncode").toString().toUpper(); + QString collectorNumber = card.value("collectorNumber").toString(); + + outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; + } + + list->loadFromStream_Plain(outStream, false); + list->resolveSetNameAndNumberToProviderID(); + + return list; + } +}; + +class MoxfieldJsonParser : public IJsonDeckParser +{ +public: + DeckLoader *parse(const QJsonObject &obj) override + { + DeckLoader *list = new DeckLoader(); + + QString deckName = obj.value("name").toString(); + QString deckDescription = obj.value("description").toString(); + + list->setName(deckName); + list->setComments(deckDescription); + + QString outputText; + QTextStream outStream(&outputText); + + for (auto entry : obj.value("mainboard").toObject()) { + auto quantity = entry.toObject().value("quantity").toInt(); + + auto card = entry.toObject().value("card").toObject(); + QString cardName = card.value("name").toString(); + QString setName = card.value("set").toString().toUpper(); + QString collectorNumber = card.value("cn").toString(); + + outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; + } + + outStream << '\n'; + + for (auto entry : obj.value("sideboard").toObject()) { + auto quantity = entry.toObject().value("quantity").toInt(); + + auto card = entry.toObject().value("card").toObject(); + QString cardName = card.value("name").toString(); + QString setName = card.value("set").toString().toUpper(); + QString collectorNumber = card.value("cn").toString(); + + outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; + } + + list->loadFromStream_Plain(outStream, false); + list->resolveSetNameAndNumberToProviderID(); + + QJsonObject commandersObj = obj.value("commanders").toObject(); + if (!commandersObj.isEmpty()) { + for (auto it = commandersObj.begin(); it != commandersObj.end(); ++it) { + QJsonObject cardData = it.value().toObject().value("card").toObject(); + QString commanderName = cardData.value("name").toString(); + QString setName = cardData.value("set").toString().toUpper(); + QString collectorNumber = cardData.value("cn").toString(); + QString providerId = cardData.value("scryfall_id").toString(); + + list->setBannerCard(QPair(commanderName, providerId)); + list->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId); + } + } + + return list; + } +}; + +#endif // INTERFACE_JSON_DECK_PARSER_H diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp index eba0bfb0f..38a89fd02 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp @@ -6,6 +6,7 @@ #include "../../deck/deck_stats_interface.h" #include "../../dialogs/dlg_load_deck.h" #include "../../dialogs/dlg_load_deck_from_clipboard.h" +#include "../../dialogs/dlg_load_deck_from_website.h" #include "../../game/cards/card_database_manager.h" #include "../../game/cards/card_database_model.h" #include "../../server/pending_command.h" @@ -467,6 +468,28 @@ void AbstractTabDeckEditor::actPrintDeck() dlg->exec(); } +void AbstractTabDeckEditor::actLoadDeckFromWebsite() +{ + auto deckOpenLocation = confirmOpen(); + + if (deckOpenLocation == CANCELLED) { + return; + } + + DlgLoadDeckFromWebsite dlg(this); + if (!dlg.exec()) + return; + + if (deckOpenLocation == NEW_TAB) { + emit openDeckEditor(dlg.getDeck()); + } else { + setDeck(dlg.getDeck()); + setModified(true); + } + + deckMenu->setSaveStatus(true); +} + void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { // check if deck is not null diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h index 1642ac107..8179b99ed 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h @@ -102,6 +102,7 @@ protected slots: void actSaveDeckToClipboardRaw(); void actSaveDeckToClipboardRawNoSetInfo(); void actPrintDeck(); + void actLoadDeckFromWebsite(); void actExportDeckDecklist(); void actExportDeckDecklistXyz(); void actAnalyzeDeckDeckstats(); diff --git a/cockatrice/src/dialogs/dlg_load_deck_from_website.cpp b/cockatrice/src/dialogs/dlg_load_deck_from_website.cpp new file mode 100644 index 000000000..44a4432c7 --- /dev/null +++ b/cockatrice/src/dialogs/dlg_load_deck_from_website.cpp @@ -0,0 +1,145 @@ +#include "dlg_load_deck_from_website.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent) +{ + nam = new QNetworkAccessManager(this); + + layout = new QVBoxLayout(this); + setLayout(layout); + + instructionLabel = new QLabel(this); + layout->addWidget(instructionLabel); + + urlEdit = new QLineEdit(this); + urlEdit->setText(QApplication::clipboard()->text()); + + layout->addWidget(urlEdit); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + layout->addWidget(buttonBox); + + if (testValidUrl()) { + QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection); + hide(); + } + + retranslateUi(); +} + +void DlgLoadDeckFromWebsite::retranslateUi() +{ + instructionLabel->setText(tr("Paste a link to a decklist site here to import it.\n(Archidekt, Deckstats, Moxfield, " + "and TappedOut are supported.)")); +} + +bool DlgLoadDeckFromWebsite::testValidUrl() +{ + ParsedDeckInfo info; + return DeckLinkToApiTransformer::parseDeckUrl(urlEdit->text(), info); +} + +void DlgLoadDeckFromWebsite::accept() +{ + ParsedDeckInfo info; + if (DeckLinkToApiTransformer::parseDeckUrl(urlEdit->text(), info)) { + qCInfo(DlgLoadDeckFromWebsiteLog) << info.baseUrl << info.deckID << info.fullUrl; + + auto jsonParser = createParserForProvider(info.provider); + if (!jsonParser && info.provider != DeckProvider::Deckstats && info.provider != DeckProvider::TappedOut) { + qCWarning(DlgLoadDeckFromWebsiteLog) << "No parser found for provider"; + QMessageBox::warning(this, tr("Load Deck from Website"), + tr("No parser available for this deck provider.\n (Archidekt, Deckstats, Moxfield, " + "and TappedOut are supported.)")); + QDialog::reject(); + return; + } + + QNetworkRequest request(QUrl(info.fullUrl)); + QNetworkReply *reply = nam->get(request); + + QEventLoop loop; + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(DlgLoadDeckFromWebsiteLog) << "Network error:" << reply->errorString(); + QMessageBox::warning(this, tr("Load Deck from Website"), tr("Network error: %1").arg(reply->errorString())); + reply->deleteLater(); + QDialog::reject(); + return; + } + + QByteArray responseData = reply->readAll(); + reply->deleteLater(); + + // Special handling for Deckstats and TappedOut .txt + if (info.provider == DeckProvider::Deckstats || info.provider == DeckProvider::TappedOut) { + QString deckText = QString::fromUtf8(responseData); + if (deckText.isEmpty()) { + qCWarning(DlgLoadDeckFromWebsiteLog) << "Response is empty"; + QMessageBox::warning(this, tr("Load Deck from Website"), tr("Received empty deck data.")); + QDialog::reject(); + return; + } + + // Parse the plain text deck here + DeckLoader *loader = new DeckLoader(); + QTextStream stream(&deckText); + loader->loadFromStream_Plain(stream, false); + loader->resolveSetNameAndNumberToProviderID(); + deck = loader; + + QDialog::accept(); + return; + } + + // Normal JSON parsing for other providers + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qCWarning(DlgLoadDeckFromWebsiteLog) << "JSON parse error:" << parseError.errorString(); + QMessageBox::warning(this, tr("Load Deck from Website"), + tr("Failed to parse deck data: %1").arg(parseError.errorString())); + QDialog::reject(); + return; + } + + deck = jsonParser->parse(doc.object()); + QDialog::accept(); + + } else { + qCInfo(DlgLoadDeckFromWebsiteLog) << "URL not recognized"; + QMessageBox::warning(this, tr("Load Deck from Website"), + tr("The provided URL is not recognized as a valid deck URL.\n" + "Valid deck URLs look like this:\n\n" + "https://archidekt.com/decks/9999999\n" + "https://deckstats.net/decks/99999/9999999-your-deck-name/en\n" + "https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz\n" + "https://tappedout.net/mtg-decks/your-deck-name/")); + QDialog::reject(); + } +} + +QSharedPointer DlgLoadDeckFromWebsite::createParserForProvider(DeckProvider provider) +{ + switch (provider) { + case DeckProvider::Archidekt: + return QSharedPointer(new ArchidektJsonParser()); + case DeckProvider::Moxfield: + return QSharedPointer(new MoxfieldJsonParser()); + default: + return QSharedPointer(nullptr); + } +} \ No newline at end of file diff --git a/cockatrice/src/dialogs/dlg_load_deck_from_website.h b/cockatrice/src/dialogs/dlg_load_deck_from_website.h new file mode 100644 index 000000000..5810c8ecc --- /dev/null +++ b/cockatrice/src/dialogs/dlg_load_deck_from_website.h @@ -0,0 +1,40 @@ +#ifndef DLG_LOAD_DECK_FROM_WEBSITE_H +#define DLG_LOAD_DECK_FROM_WEBSITE_H + +#include "../client/network/parsers/deck_link_to_api_transformer.h" +#include "../client/network/parsers/interface_json_deck_parser.h" + +#include +#include +#include +#include +#include + +inline Q_LOGGING_CATEGORY(DlgLoadDeckFromWebsiteLog, "dlg_load_deck_from_website"); + +class DlgLoadDeckFromWebsite : public QDialog +{ + Q_OBJECT +public: + explicit DlgLoadDeckFromWebsite(QWidget *parent); + void retranslateUi(); + bool testValidUrl(); + DeckLoader *deck; + + DeckLoader *getDeck() + { + return deck; + } + +private: + QNetworkAccessManager *nam; + QVBoxLayout *layout; + QLabel *instructionLabel; + QLineEdit *urlEdit; + +public slots: + void accept() override; + QSharedPointer createParserForProvider(DeckProvider provider); +}; + +#endif // DLG_LOAD_DECK_FROM_WEBSITE_H diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index 4afd07943..a5552da44 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -5,6 +5,7 @@ #include "../../deck/deck_loader.h" #include "../../dialogs/dlg_load_deck.h" #include "../../dialogs/dlg_load_deck_from_clipboard.h" +#include "../../dialogs/dlg_load_deck_from_website.h" #include "../../dialogs/dlg_load_remote_deck.h" #include "../../server/pending_command.h" #include "../../settings/cache_settings.h" @@ -54,6 +55,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) loadLocalButton = new QPushButton; loadRemoteButton = new QPushButton; loadFromClipboardButton = new QPushButton; + loadFromWebsiteButton = new QPushButton; unloadDeckButton = new QPushButton; readyStartButton = new ToggleButton; forceStartGameButton = new QPushButton; @@ -62,6 +64,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) connect(loadLocalButton, &QPushButton::clicked, this, &DeckViewContainer::loadLocalDeck); connect(loadRemoteButton, &QPushButton::clicked, this, &DeckViewContainer::loadRemoteDeck); connect(loadFromClipboardButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromClipboard); + connect(loadFromWebsiteButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromWebsite); connect(readyStartButton, &QPushButton::clicked, this, &DeckViewContainer::readyStart); connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck); connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart); @@ -72,6 +75,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) buttonHBox->addWidget(loadLocalButton); buttonHBox->addWidget(loadRemoteButton); buttonHBox->addWidget(loadFromClipboardButton); + buttonHBox->addWidget(loadFromWebsiteButton); buttonHBox->addWidget(unloadDeckButton); buttonHBox->addWidget(readyStartButton); buttonHBox->addWidget(sideboardLockButton); @@ -123,6 +127,7 @@ void DeckViewContainer::retranslateUi() loadLocalButton->setText(tr("Load deck...")); loadRemoteButton->setText(tr("Load remote deck...")); loadFromClipboardButton->setText(tr("Load from clipboard...")); + loadFromWebsiteButton->setText(tr("Load from website...")); unloadDeckButton->setText(tr("Unload deck")); readyStartButton->setText(tr("Ready to start")); forceStartGameButton->setText(tr("Force start")); @@ -154,6 +159,7 @@ void DeckViewContainer::switchToDeckSelectView() setVisibility(loadLocalButton, true); setVisibility(loadRemoteButton, !parentGame->getIsLocalGame()); setVisibility(loadFromClipboardButton, true); + setVisibility(loadFromWebsiteButton, true); setVisibility(unloadDeckButton, false); setVisibility(readyStartButton, false); setVisibility(sideboardLockButton, false); @@ -179,6 +185,7 @@ void DeckViewContainer::switchToDeckLoadedView() setVisibility(loadLocalButton, false); setVisibility(loadRemoteButton, false); setVisibility(loadFromClipboardButton, false); + setVisibility(loadFromWebsiteButton, false); setVisibility(unloadDeckButton, true); setVisibility(readyStartButton, true); setVisibility(sideboardLockButton, true); @@ -309,6 +316,18 @@ void DeckViewContainer::loadFromClipboard() loadDeckFromDeckLoader(deck); } +void DeckViewContainer::loadFromWebsite() +{ + auto dlg = DlgLoadDeckFromWebsite(this); + + if (!dlg.exec()) { + return; + } + + DeckLoader *deck = dlg.getDeck(); + loadDeckFromDeckLoader(deck); +} + void DeckViewContainer::deckSelectFinished(const Response &r) { const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); diff --git a/cockatrice/src/game/deckview/deck_view_container.h b/cockatrice/src/game/deckview/deck_view_container.h index d25a43d0d..0221570af 100644 --- a/cockatrice/src/game/deckview/deck_view_container.h +++ b/cockatrice/src/game/deckview/deck_view_container.h @@ -44,7 +44,7 @@ class DeckViewContainer : public QWidget Q_OBJECT private: QVBoxLayout *deckViewLayout; - QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton; + QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton, *loadFromWebsiteButton; QPushButton *unloadDeckButton, *forceStartGameButton; ToggleButton *readyStartButton, *sideboardLockButton; DeckView *deckView; @@ -60,6 +60,7 @@ private slots: void loadLocalDeck(); void loadRemoteDeck(); void loadFromClipboard(); + void loadFromWebsite(); void unloadDeck(); void readyStart(); void forceStart();