From a6b590df61aa6439e509ea24d4e7cbc3a0f58f97 Mon Sep 17 00:00:00 2001 From: leriomaggio Date: Sun, 22 Aug 2021 23:53:41 +0100 Subject: [PATCH] New version of the Deck Import with support for new tokens, and more First round of improvements to deck import now including the current game type in the title - and so keep track of current deck format. This will be particularly relevant for the improved DeckRecognizer which incorporates a new validation mechanism for on-the-fly card validation. Now Deck Importer also include a very thorough set of instructions as welcome message (still WIP - to be updated and refined!) Last but not least, there is automatic option to Import cards, or to Replace current deck (if any) --- .../forge/screens/deckeditor/DeckImport.java | 242 +++++++++++++----- 1 file changed, 183 insertions(+), 59 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java index c295a55402a..a9d97818409 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java @@ -26,12 +26,14 @@ import javax.swing.BorderFactory; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import forge.deck.Deck; -import forge.deck.DeckBase; -import forge.deck.DeckImportController; -import forge.deck.DeckRecognizer; +import forge.StaticData; +import forge.card.CardEdition; +import forge.deck.*; import forge.deck.DeckRecognizer.TokenType; +import forge.game.GameFormat; +import forge.game.GameType; import forge.item.InventoryItem; +import forge.model.FModel; import forge.screens.deckeditor.controllers.ACEditorBase; import forge.toolbox.FButton; import forge.toolbox.FCheckBox; @@ -45,7 +47,7 @@ import forge.util.Localizer; import forge.view.FDialog; /** - * + * * Dialog for quick import of decks. * * @param @@ -56,26 +58,56 @@ public class DeckImport ex private final FTextArea txtInput = new FTextArea(); private static final String STYLESHEET = ""; + // TODO: Add localisation support private static final String HTML_WELCOME_TEXT = "" + + "" + DeckImport.STYLESHEET - + "

You'll see recognized cards here

" - + "
Legend
" - + "
    " - + "
  • Recognized cards will be shown in green. These cards will be auto-imported into a new deck
  • " - + "
  • Lines which seem to be cards but are either misspelled or unsupported by Forge, are shown in dark-red
  • " - + "
  • Lines that appear unsignificant will be shown in gray

  • " + "
" - + "
Choosing source
" - + "

In most cases when you paste from clipboard a carefully selected area of a webpage, it works perfectly.

" - + "

Sometimes to filter out unneeded data you may have to export deck in MTGO format, and paste here downloaded file contents.

" - + "

Sideboard recognition is supported. Make sure that the sideboard cards are listed after a line that contains the word 'Sideboard'

" + + "" + + "" + + "

How to use the Deck Importer

" + + "

Quick Instructions:\n" + + "Using the Deck Importer is very simple: " + + "just type or paste the names of the cards you want (one per line in the Card List), " + + "and the Importer will automatically create the Decklist with M:TG cards in Forge." + + "You could also specify how many copies of each card you want (default: 1), " + + "and their corresponding Edition.\nIf No Edition is specified, the card print will be " + + "selected automatically according to the Card Art Preference option in Game Settings.\n\n" + + "For example: \"4 Power Sink TMP\" will import:" + + "

  • 4 copies of Power Sink from Tempest.
" + + "Each line in the list will be processed on-the-fly, and rendered in the Decklist with the following " + + "color-codes:" + + "
    " + + "
  • Card Recognized: Successful match in the database.
  • " + + "
  • Card Not Found, or Not Supported yet in Forge.
  • " + + "
  • General text or Comment: Simply ignored by the Importer.
  • " + + "

" + + "

Additional Options:" + + "

    " + + "
  1. Deck Name: you can specify a name for your deck . Just type " + + "Name: <NAME OF YOUR DECK> in the Card List;
  2. " + + "
  3. Card types: you can organise your list by types, e.g. " + + "Creature, Instant, Land, Sorcery;
  4. " + + "
  5. Deck Section: Similarly, you can organise your list by Deck Sections, " + + "e.g. Main, Sideboard;
  6. " + + "
  7. Collector Number: You can identify a specific Card Print by including its CollNr., " + + "e.g. 20 Island M21 265
  8. " + + "

" + + "

Deck Formats:" + + "Card Lists in the following formats are supported : MTG Arena (.MTGA); " + + "MTG Goldfish (.MTGO); TappedOut; DeckStats.net; .dec files.

" + + "" + ""; private final FHtmlViewer htmlOutput = new FHtmlViewer(DeckImport.HTML_WELCOME_TEXT); @@ -83,30 +115,49 @@ public class DeckImport ex private final FScrollPane scrollOutput = new FScrollPane(this.htmlOutput, false); private final FLabel summaryMain = new FLabel.Builder().text(Localizer.getInstance().getMessage("lblImportedDeckSummay")).build(); private final FLabel summarySide = new FLabel.Builder().text(Localizer.getInstance().getMessage("lblSideboardSummayLine")).build(); - private final FButton cmdAccept = new FButton(Localizer.getInstance().getMessage("lblImportDeck")); private final FButton cmdCancel = new FButton(Localizer.getInstance().getMessage("lblCancel")); - private final FCheckBox newEditionCheck = new FCheckBox(Localizer.getInstance().getMessage("lblImportLatestVersionCard"), true); - private final FCheckBox dateTimeCheck = new FCheckBox(Localizer.getInstance().getMessage("lblUseOnlySetsReleasedBefore"), false); - private final FCheckBox onlyCoreExpCheck = new FCheckBox(Localizer.getInstance().getMessage("lblUseOnlyCoreAndExpansionSets"), true); - private final FComboBox monthDropdown = new FComboBox<>(); //don't need wrappers since skin can't change while this dialog is open + private FButton cmdAccept; // Not initialised as label will be adaptive. + private final FCheckBox dateTimeCheck = new FCheckBox(Localizer.getInstance().getMessage("lblUseOnlySetsReleasedBefore"), false); + private final FCheckBox replaceDeckCheckbox = new FCheckBox(Localizer.getInstance().getMessage("lblReplaceDeck"), false); + private final DeckFormat deckFormat; + + //don't need wrappers since skin can't change while this dialog is open + private final FComboBox monthDropdown = new FComboBox<>(); private final FComboBox yearDropdown = new FComboBox<>(); private final DeckImportController controller; private final ACEditorBase host; + private final String IMPORT_CARDS_CMD_LABEL = Localizer.getInstance().getMessage("lblImportCardsCmd"); + private final String IMPORT_DECK_CMD_LABEL = Localizer.getInstance().getMessage("lblImportDeckCmd"); + private final String REPLACE_CARDS_CMD_LABEL = Localizer.getInstance().getMessage("lblReplaceCardsCmd"); + + + public DeckImport(final ACEditorBase g) { + boolean currentDeckIsNotEmpty = !(g.getDeckController().isEmpty()); + DeckFormat currentDeckFormat = g.getGameType().getDeckFormat(); + this.deckFormat = currentDeckFormat; + GameType currentGameType = g.getGameType(); + // get the game format with the same name of current game type (if any) + GameFormat currentGameFormat = FModel.getFormats().get(currentGameType.name()); + if (currentGameFormat == null) + currentGameFormat = FModel.getFormats().get("Vintage"); // default for constructed + List allowedSetCodes = currentGameFormat.getAllowedSetCodes(); + this.controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, + currentDeckIsNotEmpty, allowedSetCodes, currentDeckFormat); + String cmdAcceptLabel = currentDeckIsNotEmpty ? IMPORT_CARDS_CMD_LABEL : IMPORT_DECK_CMD_LABEL; + this.cmdAccept = new FButton(cmdAcceptLabel); - public DeckImport(final ACEditorBase g, final boolean allowCardsFromAllSets) { - this.controller = new DeckImportController(!g.getDeckController().isEmpty(), - newEditionCheck, dateTimeCheck, onlyCoreExpCheck, monthDropdown, yearDropdown); this.host = g; - final int wWidth = 700; - final int wHeight = 600; + final int wWidth = 900; + final int wHeight = 900; this.setPreferredSize(new java.awt.Dimension(wWidth, wHeight)); this.setSize(wWidth, wHeight); - this.setTitle(Localizer.getInstance().getMessage("lblDeckImporter")); + String gameTypeName = String.format(" %s", currentGameType.name()); + this.setTitle(Localizer.getInstance().getMessage("lblDeckImporter") + gameTypeName); txtInput.setFocusable(true); txtInput.setEditable(true); @@ -118,19 +169,21 @@ public class DeckImport ex this.scrollOutput.setViewportBorder(BorderFactory.createLoweredBevelBorder()); this.add(this.scrollInput, "cell 0 0, w 50%, growy, pushy"); - this.add(this.newEditionCheck, "cell 0 1, w 50%, ax c"); this.add(this.dateTimeCheck, "cell 0 2, w 50%, ax c"); this.add(monthDropdown, "cell 0 3, w 20%, ax left, split 2, pad 0 4 0 0"); this.add(yearDropdown, "w 15%"); - this.onlyCoreExpCheck.setSelected(!allowCardsFromAllSets); - this.add(this.onlyCoreExpCheck, "cell 0 4, w 50%, ax c"); - this.add(this.scrollOutput, "cell 1 0, w 50%, growy, pushy"); this.add(this.summaryMain, "cell 1 1, label"); this.add(this.summarySide, "cell 1 2, label"); + if (currentDeckIsNotEmpty){ + // Disabled Default behaviour to replace current deck: bulk import cards into the existing deck! + //this.replaceDeckCheckbox.setSelected(currentDeckIsNotEmpty); + this.add(this.replaceDeckCheckbox, "cell 1 3, align r, ax r"); + } + this.add(this.cmdAccept, "cell 1 4, split 2, w 150, align r, h 26"); this.add(this.cmdCancel, "w 150, h 26"); @@ -145,16 +198,27 @@ public class DeckImport ex @SuppressWarnings("unchecked") @Override public void actionPerformed(final ActionEvent e) { - final Deck deck = controller.accept(); + String currentDeckName = g.getDeckController().getModelName(); + final Deck deck = controller.accept(currentDeckName); if (deck == null) { return; } + // If the soon-to-import card list hasn't got any name specified in the list + // we set it to the current one (if any) or set a new one. + // In this way, if this deck will replace the current one, the name will be kept the same! + if (!deck.hasName()){ + if (currentDeckName.equals("")) + deck.setName("New Deck"); // TODO: try to generate a deck name? + else + deck.setName(currentDeckName); + } - DeckImport.this.host.getDeckController().loadDeck(deck); + DeckImport.this.host.getDeckController().loadDeck(deck, controller.getReplacingDeck()); DeckImport.this.processWindowEvent(new WindowEvent(DeckImport.this, WindowEvent.WINDOW_CLOSING)); } }); final ActionListener updateDateCheck = new ActionListener() { - @Override public void actionPerformed(final ActionEvent e) { + @Override + public void actionPerformed(final ActionEvent e) { final boolean isSel = dateTimeCheck.isSelected(); monthDropdown.setEnabled(isSel); yearDropdown.setEnabled(isSel); @@ -169,14 +233,23 @@ public class DeckImport ex } }; - this.newEditionCheck.addActionListener(reparse); - this.onlyCoreExpCheck.addActionListener(reparse); + final ActionListener toggleDeckReplace = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { toggleDeckReplaceOption(); } + }; + this.yearDropdown.addActionListener(reparse); this.monthDropdown.addActionListener(reparse); updateDateCheck.actionPerformed(null); // update actual state this.txtInput.getDocument().addDocumentListener(new OnChangeTextUpdate()); this.cmdAccept.setEnabled(false); + + + if (currentDeckIsNotEmpty){ + this.replaceDeckCheckbox.setSelected(false); + this.replaceDeckCheckbox.addActionListener(toggleDeckReplace); + } } /** @@ -208,11 +281,17 @@ public class DeckImport ex updateSummaries(tokens); } + private void toggleDeckReplaceOption(){ + boolean replaceDeckStatus = this.replaceDeckCheckbox.isSelected(); + this.controller.setReplacingDeck(replaceDeckStatus); + String cmdAcceptLabel = replaceDeckStatus ? this.REPLACE_CARDS_CMD_LABEL : this.IMPORT_CARDS_CMD_LABEL; + this.cmdAccept.setText(cmdAcceptLabel); + } + private void displayTokens(final List tokens) { - if (tokens.isEmpty()) { + if (tokens.isEmpty() || hasOnlyComment(tokens)) { htmlOutput.setText(HTML_WELCOME_TEXT); - } - else { + } else { final StringBuilder sbOut = new StringBuilder(""); sbOut.append(DeckImport.STYLESHEET); for (final DeckRecognizer.Token t : tokens) { @@ -223,6 +302,14 @@ public class DeckImport ex } } + private boolean hasOnlyComment(final List tokens) { + for (DeckRecognizer.Token token : tokens) { + if (token.getType() != TokenType.Comment && token.getType() != TokenType.UnknownText) + return false; + } + return true; + } + private void updateSummaries(final List tokens) { final int[] cardsOk = new int[2]; final int[] cardsUnknown = new int[2]; @@ -234,7 +321,7 @@ public class DeckImport ex if (t.getType() == TokenType.UnknownCard) { cardsUnknown[idx] += t.getNumber(); } - if ((t.getType() == TokenType.SectionName) && t.getText().toLowerCase().contains("side")) { + if ((t.getType() == TokenType.DeckSectionName) && t.getText().toLowerCase().contains("side")) { idx = 1; } } @@ -243,20 +330,57 @@ public class DeckImport ex cmdAccept.setEnabled(cardsOk[0] > 0); } - private static String makeHtmlViewOfToken(final DeckRecognizer.Token token) { +// private static String makeHtmlViewOfToken(final DeckRecognizer.Token token) { +// switch (token.getType()) { +// case KnownCard: +// return String.format("
%s * %s [%s] %s
", token.getNumber(), token.getCard() +// .getName(), token.getCard().getEdition(), token.getCard().isFoil() ? "foil" : ""); +// case UnknownCard: +// return String.format("
%s * %s
", token.getNumber(), token.getText()); +// case SectionName: +// return String.format("
%s
", token.getText()); +// case UnknownText: +// case Comment: +// return String.format("
%s
", token.getText()); +// default: +// return ""; +// } +// } + + private String makeHtmlViewOfToken(final DeckRecognizer.Token token) { switch (token.getType()) { - case KnownCard: - return String.format("
%s * %s [%s] %s
", token.getNumber(), token.getCard() - .getName(), token.getCard().getEdition(), token.getCard().isFoil() ? "foil" : ""); - case UnknownCard: - return String.format("
%s * %s
", token.getNumber(), token.getText()); - case SectionName: - return String.format("
%s
", token.getText()); - case UnknownText: - case Comment: - return String.format("
%s
", token.getText()); - default: - return ""; + case KnownCard: + CardEdition edition = StaticData.instance().getEditions().get(token.getCard().getEdition()); + String editionName; + if (edition == null) + editionName = "Unknown Edition"; + else + editionName = edition.getName(); + // TODO: String Padding for alignment + return String.format("
%s x %s from %s (%s) %s
", + token.getNumber(), token.getCard().getName(), + editionName, token.getCard().getEdition(), + token.getCard().isFoil() ? "foil" : ""); + case UnknownCard: + return String.format("
%s x %s (%s)
", + token.getNumber(), token.getText(), + Localizer.getInstance().getMessage("lblUnknownCard")); + case IllegalCard: + return String.format("
%s x %s (%s %s)
", + token.getNumber(), token.getText(), + Localizer.getInstance().getMessage("lblIllegalCard"), + this.deckFormat.name()); + case DeckSectionName: + return String.format("
%s
", token.getText()); + case CardType: + return String.format("
%s
", token.getText()); + case DeckName: + return String.format("
Deck Name: %s
", token.getText()); + case UnknownText: + case Comment: + default: + return ""; + // return String.format("
%s
", token.getText()); } } }