forge.screens.deckeditor.DeckImport.java Source code

Java tutorial

Introduction

Here is the source code for forge.screens.deckeditor.DeckImport.java

Source

/*
 * Forge: Play Magic: the Gathering.
 * Copyright (C) 2011  Forge Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package forge.screens.deckeditor;

import forge.deck.Deck;
import forge.deck.DeckBase;
import forge.deck.DeckRecognizer;
import forge.deck.DeckRecognizer.TokenType;
import forge.deck.DeckSection;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.screens.deckeditor.controllers.ACEditorBase;
import forge.toolbox.*;
import forge.view.FDialog;

import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.ElementIterator;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * 
 * Dialog for quick import of decks.
 *
 * @param <TItem>
 * @param <TModel>
 */
public class DeckImport<TItem extends InventoryItem, TModel extends DeckBase> extends FDialog {
    private static final long serialVersionUID = -5837776824284093004L;

    private final FTextArea txtInput = new FTextArea();
    private static final String STYLESHEET = "<style>"
            + "body, h1, h2, h3, h4, h5, h6, table, tr, td, p {margin: 3px 1px; padding: 0; font-weight: "
            + "normal; font-style: normal; text-decoration: none; font-family: Arial; font-size: 10px; background-color: white;} "
            +
            // "h1 {border-bottom: solid 1px black; color: blue; font-size: 12px; margin: 3px 0 9px 0; } "
            // +
            ".comment {color: #666666;} " + ".knowncard {color: #009900;} " + ".unknowncard {color: #990000;} "
            + ".section {padding: 3px 10px; margin: 3px 0; font-weight: 700; background-color: #DDDDDD; } "
            + "</style>";
    private static final String HTML_WELCOME_TEXT = "<html>" + DeckImport.STYLESHEET
            + "<h3>You'll see recognized cards here</h3>" + "<div class='section'>Legend</div>" + "<ul>"
            + "<li class='knowncard'>Recognized cards will be shown in green. These cards will be auto-imported into a new deck<BR></li>"
            + "<li class='unknowncard'>Lines which seem to be cards but are either misspelled or unsupported by Forge, are shown in dark-red<BR></li>"
            + "<li class='comment'>Lines that appear unsignificant will be shown in gray<BR><BR></li>" + "</ul>"
            + "<div class='section'>Choosing source</div>"
            + "<p>In most cases when you paste from clipboard a carefully selected area of a webpage, it works perfectly.</p>"
            + "<p>Sometimes to filter out unneeded data you may have to export deck in MTGO format, and paste here downloaded file contents.</p>"
            + "<p>Sideboard recognition is supported. Make sure that the sideboard cards are listed after a line that contains the word 'Sideboard'</p>"
            + "</html>";

    private final FHtmlViewer htmlOutput = new FHtmlViewer(DeckImport.HTML_WELCOME_TEXT);
    private final FScrollPane scrollInput = new FScrollPane(this.txtInput, false);
    private final FScrollPane scrollOutput = new FScrollPane(this.htmlOutput, false);
    private final FLabel summaryMain = new FLabel.Builder().text("Imported deck summary will appear here").build();
    private final FLabel summarySide = new FLabel.Builder().text("Line for sideboard summary").build();
    private final FButton cmdAccept = new FButton("Import Deck");
    private final FButton cmdCancel = new FButton("Cancel");
    private final FCheckBox newEditionCheck = new FCheckBox("Import latest version of card", true);
    private final FCheckBox dateTimeCheck = new FCheckBox("Use only sets released before:", false);
    private final FCheckBox onlyCoreExpCheck = new FCheckBox("Use only core and expansion sets", true);

    private final FComboBox<String> monthDropdown = new FComboBox<String>(); //don't need wrappers since skin can't change while this dialog is open
    private final FComboBox<Integer> yearDropdown = new FComboBox<Integer>();

    /** The tokens. */
    private final List<DeckRecognizer.Token> tokens = new ArrayList<DeckRecognizer.Token>();

    private final ACEditorBase<TItem, TModel> host;

    /**
     * Instantiates a new deck import.
     * 
     * @param g
     *            the g
     */
    public DeckImport(final ACEditorBase<TItem, TModel> g) {
        this.host = g;

        final int wWidth = 700;
        final int wHeight = 600;

        this.setPreferredSize(new java.awt.Dimension(wWidth, wHeight));
        this.setSize(wWidth, wHeight);
        this.setTitle("Deck Importer");

        txtInput.setFocusable(true);
        txtInput.setEditable(true);

        FSkin.SkinColor foreColor = FSkin.getColor(FSkin.Colors.CLR_TEXT);
        this.scrollInput.setBorder(new FSkin.TitledSkinBorder(BorderFactory.createEtchedBorder(),
                "Paste or type a decklist", foreColor));
        this.scrollOutput.setBorder(new FSkin.TitledSkinBorder(BorderFactory.createEtchedBorder(),
                "Expect the recognized lines to appear", foreColor));
        this.scrollInput.setViewportBorder(BorderFactory.createLoweredBevelBorder());
        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%");
        fillDateDropdowns();

        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");

        this.add(this.cmdAccept, "cell 1 4, split 2, w 150, align r, h 26");
        this.add(this.cmdCancel, "w 150, h 26");

        this.cmdCancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                DeckImport.this.processWindowEvent(new WindowEvent(DeckImport.this, WindowEvent.WINDOW_CLOSING));
            }
        });

        this.cmdAccept.addActionListener(new ActionListener() {
            @SuppressWarnings("unchecked")
            @Override
            public void actionPerformed(final ActionEvent e) {
                final String warning = "This will replace contents of your currently open deck with whatever you are importing. Proceed?";
                if (!FOptionPane.showConfirmDialog(warning, "Replacing old deck")) {
                    return;
                }
                final Deck toSet = DeckImport.this.buildDeck();
                DeckImport.this.host.getDeckController().setModel((TModel) toSet);
                DeckImport.this.processWindowEvent(new WindowEvent(DeckImport.this, WindowEvent.WINDOW_CLOSING));
            }
        });

        ActionListener updateDateCheck = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                boolean isSel = dateTimeCheck.isSelected();
                monthDropdown.setEnabled(isSel);
                yearDropdown.setEnabled(isSel);
                parseAndDisplay();
            }
        };
        this.dateTimeCheck.addActionListener(updateDateCheck);

        ActionListener reparse = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                parseAndDisplay();
            }
        };
        this.newEditionCheck.addActionListener(reparse);
        this.onlyCoreExpCheck.addActionListener(reparse);
        this.yearDropdown.addActionListener(reparse);
        this.monthDropdown.addActionListener(reparse);
        updateDateCheck.actionPerformed(null); // update actual state

        this.txtInput.getDocument().addDocumentListener(new OnChangeTextUpdate());
        this.cmdAccept.setEnabled(false);
    }

    /**
     * TODO: Write javadoc for this method.
     */
    private void fillDateDropdowns() {
        DateFormatSymbols dfs = new DateFormatSymbols();
        monthDropdown.removeAllItems();
        String[] months = dfs.getMonths();
        for (String monthName : months)
            if (!StringUtils.isBlank(monthName))
                monthDropdown.addItem(monthName);
        int yearNow = Calendar.getInstance().get(Calendar.YEAR);
        for (int i = yearNow; i >= 1993; i--)
            yearDropdown.addItem(Integer.valueOf(i));
    }

    private void readInput() {
        this.tokens.clear();
        final ElementIterator it = new ElementIterator(this.txtInput.getDocument().getDefaultRootElement());
        Element e;

        DeckRecognizer recognizer = new DeckRecognizer(newEditionCheck.isSelected(), onlyCoreExpCheck.isSelected(),
                FModel.getMagicDb().getCommonCards());
        if (dateTimeCheck.isSelected()) {
            recognizer.setDateConstraint(monthDropdown.getSelectedIndex(),
                    (Integer) yearDropdown.getSelectedItem());
        }
        while ((e = it.next()) != null) {
            if (!e.isLeaf()) {
                continue;
            }
            final int rangeStart = e.getStartOffset();
            final int rangeEnd = e.getEndOffset();
            try {
                final String line = this.txtInput.getText(rangeStart, rangeEnd - rangeStart);
                this.tokens.add(recognizer.recognizeLine(line));
            } catch (final BadLocationException ex) {
            }
        }
    }

    private void displayTokens() {
        if (this.tokens.isEmpty())
            this.htmlOutput.setText(HTML_WELCOME_TEXT);
        else {
            final StringBuilder sbOut = new StringBuilder("<html>");
            sbOut.append(DeckImport.STYLESHEET);
            for (final DeckRecognizer.Token t : this.tokens) {
                sbOut.append(this.makeHtmlViewOfToken(t));
            }
            sbOut.append("</html>");
            this.htmlOutput.setText(sbOut.toString());
        }
    }

    private void updateSummaries() {
        final int[] cardsOk = new int[2];
        final int[] cardsUnknown = new int[2];
        int idx = 0;
        for (final DeckRecognizer.Token t : this.tokens) {
            if (t.getType() == TokenType.KnownCard) {
                cardsOk[idx] += t.getNumber();
            }
            if (t.getType() == TokenType.UnknownCard) {
                cardsUnknown[idx] += t.getNumber();
            }
            if ((t.getType() == TokenType.SectionName) && t.getText().toLowerCase().contains("side")) {
                idx = 1;
            }
        }
        this.summaryMain
                .setText(String.format("Main: %d cards recognized, %d unknown cards", cardsOk[0], cardsUnknown[0]));
        this.summarySide.setText(
                String.format("Sideboard: %d cards recognized, %d unknown cards", cardsOk[1], cardsUnknown[1]));
        this.cmdAccept.setEnabled(cardsOk[0] > 0);
    }

    private Deck buildDeck() {
        final Deck result = new Deck();
        boolean isMain = true;
        for (final DeckRecognizer.Token t : this.tokens) {
            final DeckRecognizer.TokenType type = t.getType();
            if ((type == DeckRecognizer.TokenType.SectionName) && t.getText().toLowerCase().contains("side")) {
                isMain = false;
            }
            if (type != DeckRecognizer.TokenType.KnownCard) {
                continue;
            }
            final PaperCard crd = t.getCard();
            if (isMain) {
                result.getMain().add(crd, t.getNumber());
            } else {
                result.getOrCreate(DeckSection.Sideboard).add(crd, t.getNumber());
            }
        }
        return result;
    }

    protected void parseAndDisplay() {
        readInput();
        displayTokens();
        updateSummaries();
    }

    /**
     * The Class OnChangeTextUpdate.
     */
    protected class OnChangeTextUpdate implements DocumentListener {
        private void onChange() {
            DeckImport.this.parseAndDisplay();
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * javax.swing.event.DocumentListener#insertUpdate(javax.swing.event
         * .DocumentEvent)
         */
        @Override
        public final void insertUpdate(final DocumentEvent e) {
            this.onChange();
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * javax.swing.event.DocumentListener#removeUpdate(javax.swing.event
         * .DocumentEvent)
         */
        @Override
        public final void removeUpdate(final DocumentEvent e) {
            this.onChange();
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * javax.swing.event.DocumentListener#changedUpdate(javax.swing.event
         * .DocumentEvent)
         */
        @Override
        public void changedUpdate(final DocumentEvent e) {
        } // Happend only on ENTER pressed
    }

    private String makeHtmlViewOfToken(final DeckRecognizer.Token token) {
        switch (token.getType()) {
        case KnownCard:
            return String.format("<div class='knowncard'>%s * %s [%s] %s</div>", token.getNumber(),
                    token.getCard().getName(), token.getCard().getEdition(),
                    token.getCard().isFoil() ? "<i>foil</i>" : "");
        case UnknownCard:
            return String.format("<div class='unknowncard'>%s * %s</div>", token.getNumber(), token.getText());
        case SectionName:
            return String.format("<div class='section'>%s</div>", token.getText());
        case UnknownText:
        case Comment:
            return String.format("<div class='comment'>%s</div>", token.getText());
        default:
            break;
        }
        return "";
    }

}