net.sf.firemox.DeckBuilder.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.firemox.DeckBuilder.java

Source

/*
 *   Firemox is a turn based strategy simulator
 *   Copyright (C) 2003-2007 Fabrice Daugan
 *
 *   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 2 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, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package net.sf.firemox;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;

import net.sf.firemox.chart.CardColor;
import net.sf.firemox.chart.CardManaCost;
import net.sf.firemox.chart.CardTypes;
import net.sf.firemox.chart.ChartFilter;
import net.sf.firemox.chart.ChartSets;
import net.sf.firemox.chart.IChartKey;
import net.sf.firemox.chart.IDataProvider;
import net.sf.firemox.chart.datasets.Dataset;
import net.sf.firemox.clickable.target.card.CardFactory;
import net.sf.firemox.clickable.target.card.CardModel;
import net.sf.firemox.clickable.target.card.MCard;
import net.sf.firemox.deckbuilder.CardLoader;
import net.sf.firemox.deckbuilder.CardView;
import net.sf.firemox.deckbuilder.ConstraintsChecker;
import net.sf.firemox.deckbuilder.Deck;
import net.sf.firemox.deckbuilder.DeckConstraints;
import net.sf.firemox.deckbuilder.DeckReader;
import net.sf.firemox.deckbuilder.DeckRules;
import net.sf.firemox.deckbuilder.MdbLoader;
import net.sf.firemox.token.IdCardColors;
import net.sf.firemox.token.IdCommonToken;
import net.sf.firemox.token.IdConst;
import net.sf.firemox.tools.Configuration;
import net.sf.firemox.tools.Converter;
import net.sf.firemox.tools.FileFilterPlus;
import net.sf.firemox.tools.MCardCompare;
import net.sf.firemox.tools.MSaveDeck;
import net.sf.firemox.tools.MToolKit;
import net.sf.firemox.tools.Picture;
import net.sf.firemox.ui.HireListener;
import net.sf.firemox.ui.MCardTableModel;
import net.sf.firemox.ui.MListModel;
import net.sf.firemox.ui.RefreshableAdd;
import net.sf.firemox.ui.TimerGlassPane;
import net.sf.firemox.ui.UIHelper;
import net.sf.firemox.ui.component.ThreadSafeJList;
import net.sf.firemox.ui.i18n.LanguageManager;
import net.sf.firemox.ui.wizard.About;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ArrayUtils;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;

/**
 * This class is a deck builder compatible with Firemox deck format. Can
 * also export/import other formats.
 * 
 * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
 * @author <a href="mailto:goldeneyemdk@users.sourceforge.net">Sebastien Genete
 *         </a>
 * @author Jan Blaha for deck loader and statistics
 * @since 0.2 2003/12/06, settings are loaded.
 * @since 0.2 you can load a deck file.
 * @since 0.2 you can load a specified MDB file (is MagicProject card list
 *        file).
 * @since 0.3 2003/12/12, you can save a deck file.
 * @since 0.3 new deck feature.
 * @since 0.4 sort MDB file feature for optimized load.
 * @since 0.4 deck are saved with alphabetically sort.
 * @since 0.5 statistics feature added.
 * @since 0.81 deck converter added.
 * @since 0.83 cleaned code, deck constraints form added.
 * @since 0.93 pie chart added, color filtering
 * @since 0.94 use of JTable instead of JList
 */
public final class DeckBuilder extends AbstractMainForm
        implements ListSelectionListener, RefreshableAdd, IDataProvider, TableModelListener {

    private static final int WINDOW_WIDTH = 700;

    private static final int WINDOW_HEIGHT = 700;

    /**
     * Card loader bar
     */
    protected CardLoader cardLoader;

    /**
     * Load timer
     */
    protected Timer timer;

    private final TimerGlassPane timerPanel;

    private JComponent listScrollerLeft;

    /**
     * Creates new form DeckBuilder
     */
    private DeckBuilder() {
        super("DeckBuilder");
        form = this;
        timerPanel = new TimerGlassPane();
        cardLoader = new CardLoader(timerPanel);
        timer = new Timer(200, cardLoader);
        setGlassPane(timerPanel);
        try {
            setIconImage(Picture.loadImage(IdConst.IMAGES_DIR + "deckbuilder.gif"));
        } catch (Exception e) {
            // IGNORING
        }

        // Load settings
        loadSettings();

        // Initialize components
        final JMenuItem newItem = UIHelper.buildMenu("menu_db_new", 'n', this);
        newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK));

        final JMenuItem loadItem = UIHelper.buildMenu("menu_db_load", 'o', this);
        loadItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));

        final JMenuItem saveAsItem = UIHelper.buildMenu("menu_db_saveas", 'a', this);
        saveAsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F12, 0));

        final JMenuItem saveItem = UIHelper.buildMenu("menu_db_save", 's', this);
        saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));

        final JMenuItem quitItem = UIHelper.buildMenu("menu_db_exit", this);
        quitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_MASK));

        final JMenuItem deckConstraintsItem = UIHelper.buildMenu("menu_db_constraints", 'c', this);
        deckConstraintsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0));

        final JMenuItem aboutItem = UIHelper.buildMenu("menu_help_about", 'a', this);
        aboutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, InputEvent.SHIFT_MASK));

        final JMenuItem convertDCK = UIHelper.buildMenu("menu_convert_DCK_MP", this);

        final JMenu mainMenu = UIHelper.buildMenu("menu_file");
        mainMenu.add(newItem);
        mainMenu.add(loadItem);
        mainMenu.add(saveAsItem);
        mainMenu.add(saveItem);
        mainMenu.add(new JSeparator());
        mainMenu.add(quitItem);

        super.optionMenu = new JMenu("Options");

        final JMenu convertMenu = UIHelper.buildMenu("menu_convert");
        convertMenu.add(convertDCK);

        final JMenuItem helpItem = UIHelper.buildMenu("menu_help_help", 'h', this);
        helpItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));

        final JMenu helpMenu = new JMenu("?");
        helpMenu.add(helpItem);
        helpMenu.add(deckConstraintsItem);
        helpMenu.add(aboutItem);

        final JMenuBar menuBar = new JMenuBar();
        menuBar.add(mainMenu);
        initAbstractMenu();
        menuBar.add(optionMenu);
        menuBar.add(convertMenu);
        menuBar.add(helpMenu);
        setJMenuBar(menuBar);
        addWindowListener(this);

        // Build the panel containing amount of available cards
        final JLabel amountLeft = new JLabel("<html>0/?", SwingConstants.RIGHT);

        // Build the left list
        allListModel = new MListModel<MCardCompare>(amountLeft, false);
        leftList = new ThreadSafeJList(allListModel);
        leftList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        leftList.setLayoutOrientation(JList.VERTICAL);
        leftList.getSelectionModel().addListSelectionListener(this);
        leftList.addMouseListener(this);
        leftList.setVisibleRowCount(10);

        // Initialize the text field containing the amount to add
        addQtyTxt = new JTextField("1");

        // Build the "Add" button
        addButton = new JButton(LanguageManager.getString("db_add"));
        addButton.setMnemonic('a');
        addButton.setEnabled(false);

        // Build the panel containing : "Add" amount and "Add" button
        final Box addPanel = Box.createHorizontalBox();
        addPanel.add(addButton);
        addPanel.add(addQtyTxt);
        addPanel.setMaximumSize(new Dimension(32010, 26));

        // Build the panel containing the selected card name
        cardNameTxt = new JTextField();
        new HireListener(cardNameTxt, addButton, this, leftList);

        final JLabel searchLabel = new JLabel(LanguageManager.getString("db_search") + " : ");
        searchLabel.setLabelFor(cardNameTxt);

        // Build the panel containing search label and card name text field
        final Box searchPanel = Box.createHorizontalBox();
        searchPanel.add(searchLabel);
        searchPanel.add(cardNameTxt);
        searchPanel.setMaximumSize(new Dimension(32010, 26));

        listScrollerLeft = new JScrollPane(leftList);
        MToolKit.addOverlay(listScrollerLeft);

        // Build the left panel containing : list, available amount, "Add" panel
        final JPanel srcPanel = new JPanel(null);
        srcPanel.add(searchPanel);
        srcPanel.add(listScrollerLeft);
        srcPanel.add(amountLeft);
        srcPanel.add(addPanel);
        srcPanel.setMinimumSize(new Dimension(220, 200));
        srcPanel.setLayout(new BoxLayout(srcPanel, BoxLayout.Y_AXIS));

        // Initialize constraints
        constraintsChecker = new ConstraintsChecker();
        constraintsChecker.setBorder(new EtchedBorder());
        final JScrollPane constraintsCheckerScroll = new JScrollPane(constraintsChecker);
        MToolKit.addOverlay(constraintsCheckerScroll);

        // create a pane with the oracle text for the present card
        oracleText = new JLabel();
        oracleText.setPreferredSize(new Dimension(180, 200));
        oracleText.setVerticalAlignment(SwingConstants.TOP);

        final JScrollPane oracle = new JScrollPane(oracleText, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        MToolKit.addOverlay(oracle);

        // build some Pie Charts and a panel to display it
        initSets();
        datasets = new ChartSets();
        final JTabbedPane tabbedPane = new JTabbedPane();
        for (ChartFilter filter : ChartFilter.values()) {
            final Dataset dataSet = filter.createDataSet(this);
            final JFreeChart chart = new JFreeChart(null, null,
                    filter.createPlot(dataSet, painterMapper.get(filter)), false);
            datasets.addDataSet(filter, dataSet);
            ChartPanel pieChartPanel = new ChartPanel(chart, true);
            tabbedPane.add(pieChartPanel, filter.getTitle());
        }
        // add the Constraints scroll panel and Oracle text Pane to the tabbedPane
        tabbedPane.add(constraintsCheckerScroll, LanguageManager.getString("db_constraints"));
        tabbedPane.add(oracle, LanguageManager.getString("db_text"));
        tabbedPane.setSelectedComponent(oracle);

        // The toollBar for color filtering
        toolBar = new JToolBar();
        toolBar.setFloatable(false);
        final JButton clearButton = UIHelper.buildButton("clear");
        clearButton.addActionListener(this);
        toolBar.add(clearButton);
        final JToggleButton toggleColorlessButton = new JToggleButton(
                UIHelper.getTbsIcon("mana/colorless/small/" + MdbLoader.unknownSmlMana), true);
        toggleColorlessButton.setActionCommand("0");
        toggleColorlessButton.addActionListener(this);
        toolBar.add(toggleColorlessButton);
        for (int index = 1; index < IdCardColors.CARD_COLOR_NAMES.length; index++) {
            final JToggleButton toggleButton = new JToggleButton(
                    UIHelper.getTbsIcon("mana/colored/small/" + MdbLoader.coloredSmlManas[index]), true);
            toggleButton.setActionCommand(String.valueOf(index));
            toggleButton.addActionListener(this);
            toolBar.add(toggleButton);
        }

        // sorted card type combobox creation
        final List<String> idCards = new ArrayList<String>(Arrays.asList(CardFactory.exportedIdCardNames));
        Collections.sort(idCards);
        final Object[] cardTypes = ArrayUtils.addAll(new String[] { LanguageManager.getString("db_types.any") },
                idCards.toArray());
        idCardComboBox = new JComboBox(cardTypes);
        idCardComboBox.setSelectedIndex(0);
        idCardComboBox.addActionListener(this);
        idCardComboBox.setActionCommand("cardTypeFilter");

        // sorted card properties combobox creation
        final List<String> properties = new ArrayList<String>(
                CardFactory.getPropertiesName(DeckConstraints.getMinProperty(), DeckConstraints.getMaxProperty()));
        Collections.sort(properties);
        final Object[] cardProperties = ArrayUtils
                .addAll(new String[] { LanguageManager.getString("db_properties.any") }, properties.toArray());
        propertiesComboBox = new JComboBox(cardProperties);
        propertiesComboBox.setSelectedIndex(0);
        propertiesComboBox.addActionListener(this);
        propertiesComboBox.setActionCommand("propertyFilter");

        final JLabel colors = new JLabel(" " + LanguageManager.getString("colors") + " : ");
        final JLabel types = new JLabel(" " + LanguageManager.getString("types") + " : ");
        final JLabel property = new JLabel(" " + LanguageManager.getString("properties") + " : ");

        // filter Panel with colors toolBar and card type combobox
        final Box filterPanel = Box.createHorizontalBox();
        filterPanel.add(colors);
        filterPanel.add(toolBar);
        filterPanel.add(types);
        filterPanel.add(idCardComboBox);
        filterPanel.add(property);
        filterPanel.add(propertiesComboBox);

        getContentPane().add(filterPanel, BorderLayout.NORTH);

        // Destination section :

        // Build the panel containing amount of available cards
        final JLabel rightAmount = new JLabel("0/?", SwingConstants.RIGHT);
        rightAmount.setMaximumSize(new Dimension(220, 26));

        // Build the right list
        rightListModel = new MCardTableModel(new MListModel<MCardCompare>(rightAmount, true));
        rightListModel.addTableModelListener(this);
        rightList = new JTable(rightListModel);
        rightList.setShowGrid(false);
        rightList.setTableHeader(null);
        rightList.getSelectionModel().addListSelectionListener(this);
        rightList.getColumnModel().getColumn(0).setMaxWidth(25);
        rightList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);

        // Build the panel containing the selected deck
        deckNameTxt = new JTextField("loading...");
        deckNameTxt.setEditable(false);
        deckNameTxt.setBorder(null);
        final JLabel deckLabel = new JLabel(LanguageManager.getString("db_deck") + " : ");
        deckLabel.setLabelFor(deckNameTxt);
        final Box deckNamePanel = Box.createHorizontalBox();
        deckNamePanel.add(deckLabel);
        deckNamePanel.add(deckNameTxt);
        deckNamePanel.setMaximumSize(new Dimension(220, 26));

        // Initialize the text field containing the amount to remove
        removeQtyTxt = new JTextField("1");

        // Build the "Remove" button
        removeButton = new JButton(LanguageManager.getString("db_remove"));
        removeButton.setMnemonic('r');
        removeButton.addMouseListener(this);
        removeButton.setEnabled(false);

        // Build the panel containing : "Remove" amount and "Remove" button
        final Box removePanel = Box.createHorizontalBox();
        removePanel.add(removeButton);
        removePanel.add(removeQtyTxt);
        removePanel.setMaximumSize(new Dimension(220, 26));

        // Build the right panel containing : list, available amount, constraints
        final JScrollPane deskListScroller = new JScrollPane(rightList);
        MToolKit.addOverlay(deskListScroller);
        deskListScroller.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        deskListScroller.setMinimumSize(new Dimension(220, 200));
        deskListScroller.setMaximumSize(new Dimension(220, 32000));

        final Box destPanel = Box.createVerticalBox();
        destPanel.add(deckNamePanel);
        destPanel.add(deskListScroller);
        destPanel.add(rightAmount);
        destPanel.add(removePanel);
        destPanel.setMinimumSize(new Dimension(220, 200));
        destPanel.setMaximumSize(new Dimension(220, 32000));

        // Build the panel containing the name of card in picture
        cardPictureNameTxt = new JLabel("<html><i>no selected card</i>");
        final Box cardPictureNamePanel = Box.createHorizontalBox();
        cardPictureNamePanel.add(cardPictureNameTxt);
        cardPictureNamePanel.setMaximumSize(new Dimension(32010, 26));

        // Group the detail panels
        final JPanel viewCard = new JPanel(null);
        viewCard.add(cardPictureNamePanel);
        viewCard.add(CardView.getInstance());
        viewCard.add(tabbedPane);
        viewCard.setLayout(new BoxLayout(viewCard, BoxLayout.Y_AXIS));

        final Box mainPanel = Box.createHorizontalBox();
        mainPanel.add(destPanel);
        mainPanel.add(viewCard);

        // Add the main panel
        getContentPane().add(srcPanel, BorderLayout.WEST);
        getContentPane().add(mainPanel, BorderLayout.CENTER);

        // Size this frame
        getRootPane().setPreferredSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
        getRootPane().setMinimumSize(getRootPane().getPreferredSize());
        pack();
    }

    /**
     * Load a given deck file into the given list.
     * 
     * @param fileName
     *          deck file name.
     * @param names
     *          loaded cards to complete.
     */
    protected void loadDeck(String fileName, MListModel<MCardCompare> names) {
        // empty file name or deck?
        if (fileName == null || fileName.length() == 0) {
            return;
        }

        // Append the deck name
        final String deckName = FilenameUtils.removeExtension(FilenameUtils.getName(fileName));
        setTitle("DeckBuilder : " + deckName);
        deckNameTxt.setText(deckName);

        names.clear();
        datasets.removeAll();
        try {
            Deck currentDeck = DeckReader.getDeck(this, fileName);
            if (currentDeck == null) {
                // Deck loading failure
                return;
            }

            // Validate this deck
            DeckReader.validateDeck(this, currentDeck, (String) null);

            // Update the card models of resolved cards
            final FileInputStream dbStream = MdbLoader.resetMdb();
            for (MCardCompare cardCompare : currentDeck.getCards()) {
                names.add(cardCompare);
                datasets.addCard(cardCompare.getModel(dbStream), cardCompare.getAmount());
            }
            constraintsChecker.setDeck(currentDeck);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            updateChecker();
            setAsSaved();
        }
    }

    private void setCard(MCardCompare card) {
        CardView.getInstance().setCard(card);
        cardPictureNameTxt.setText(card.getName());
        FileInputStream dbStream = MdbLoader.resetMdb();
        try {
            oracleText.setText("<html>Oracle Rules:<br><br>"
                    + ((MCard) card.getCard(dbStream)).getDatabase().getProperty("card.text"));
        } catch (IOException e) {
            // ignore
            e.printStackTrace();
        }
    }

    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting()) {
            if (e.getSource() == leftList.getSelectionModel()) {
                if (leftList.getSelectedIndex() == -1 || allListModel.isEmpty()) {
                    // No selection, disable fire button.
                    addButton.setEnabled(false);
                } else {
                    // Selection, enable the fire button.
                    addButton.setEnabled(true);

                    // Update the views
                    final MCardCompare selectedCard = (MCardCompare) leftList.getSelectedValue();
                    cardNameTxt.setText(selectedCard.getName());
                    setCard(selectedCard);
                }
            } else if (e.getSource() == rightList.getSelectionModel()) {
                if (rightList.getSelectedRow() == -1) {
                    // No selection, disable fire button.
                    removeButton.setEnabled(false);
                } else {
                    // Selection, enable the fire button.
                    removeButton.setEnabled(true);

                    // Update the views
                    setCard(rightListModel.getCards().getElementAt(rightList.getSelectedRow()));
                }
            }
        }
    }

    /**
     * Exit the Application
     */
    private void exitForm() {
        verifyModification();
        if (consoleMode) {
            System.exit(0);
        } else {
            setVisible(false);
        }
    }

    /**
     * Load the deck builder from the command line.
     * 
     * @param args
     *          the command line arguments
     */
    public static void main(String[] args) {
        consoleMode = true;
        JFrame.setDefaultLookAndFeelDecorated(true);
        JDialog.setDefaultLookAndFeelDecorated(true);
        autoLoad();
    }

    /**
     * Load the deck builder from Magic class.
     */
    public static void loadFromMagic() {
        consoleMode = false;
        autoLoad();
    }

    /**
     * Load the deck builder
     */
    private static void autoLoad() {
        if (form == null) {
            form = new DeckBuilder();
            form.setLocation((form.getToolkit().getScreenSize().width - WINDOW_WIDTH) / 2,
                    (form.getToolkit().getScreenSize().height - WINDOW_HEIGHT) / 2);
        }
        form.setVisible(true);
        if (form.allListModel.isEmpty()) {
            form.timerPanel.setVisible(true);
            form.timer.start();
            new Thread(
                    /**
                     * This class refresh the loading bar
                     */
                    new Runnable() {
                        public void run() {
                            // Populate available cards and fill them into the left list
                            form.readAvailableCards(form.allListModel);

                            // Load the last deck file
                            if (Configuration.getString("decks.deck(0)", "").length() > 0) {
                                form.loadDeck(Configuration.getString("decks.deck(0)"),
                                        form.rightListModel.getCards());
                                form.rightListModel.refresh();
                            } else {
                                form.setAsNew();
                            }

                            form.timer.stop();
                            form.cardLoader.resetCounter();
                        }
                    }).start();
        }
    }

    /**
     * Adding a card to the deck
     * 
     * @param unitary
     */
    public void refreshAddComponent(boolean unitary) {

        if (leftList.isSelectionEmpty())
            return;

        String name = leftList.getSelectedValue().toString();
        int qty = 1;
        if (!unitary) {
            try {
                qty = Integer.parseInt(addQtyTxt.getText());
            } catch (Exception e1) {
                JOptionPane.showMessageDialog(form, "Malformed integer for quantity", "Internal Error",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }
        }
        // User didn't type in a unique name...
        if (name.length() == 0) {
            cardNameTxt.requestFocusInWindow();
            return;
        }

        int index = rightListModel.getCards().indexOf(name);
        final MCardCompare card;
        if (index == -1) {
            // no selection, so insert at beginning
            int allIndex = allListModel.indexOf(name);
            assert allIndex != -1;
            card = allListModel.getElementAt(allIndex).clone();
            card.add(qty);
            rightListModel.getCards().insertElementAt(card, 0);
            rightListModel.fireTableRowsInserted(0, 0);

            // Reset the text field.
            cardNameTxt.requestFocusInWindow();
            cardNameTxt.setText("");
        } else { // add after the selected item
            rightListModel.getCards().getElementAt(index).add(qty);
            rightListModel.fireTableCellUpdated(index, 0);
        }

        // Update the card models of resolved cards
        final FileInputStream dbStream = MdbLoader.resetMdb();
        datasets.addCard(getCardModel(name, dbStream), qty);

        // deck list has been modified
        modifiedSinceSave = true;
    }

    /**
     * removes cards from current deck according to the remove panel
     */
    private void removeCardFromDeck() {

        final MCardCompare card = rightListModel.getCards().getElementAt(rightList.getSelectedRow());

        int qty = 1;

        try {
            qty = Integer.parseInt(removeQtyTxt.getText());
        } catch (Exception e1) {
            JOptionPane.showMessageDialog(form, "Malformed integer for quantity", "Internal Error",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        card.remove(qty);

        if (card.getAmount() <= 0) {
            rightListModel.getCards().remove(rightList.getSelectedRow());
            rightListModel.fireTableRowsDeleted(0, rightList.getRowCount());
        } else
            rightListModel.fireTableCellUpdated(rightListModel.getCards().indexOf(card.getName()), 0);

        final FileInputStream dbStream = MdbLoader.resetMdb();
        datasets.removeCard(getCardModel(card.getName(), dbStream), qty);
        modifiedSinceSave = true;

    }

    /**
     * Update the checker and repaint the right list.
     */
    private void updateChecker() {
        constraintsChecker.updateCheckers();
        rightList.repaint();
    }

    /**
     * Return the associated cardModel to the given real card name. The card is
     * searched in the input starting from a given offset.
     * 
     * @param realCardName
     *          the card name as written in the MDB stream.
     * @param input
     *          the input stream of current MDB.
     * @return the corresponding CardModel instance, or null if the card has not
     *         been matched.
     */
    private CardModel getCardModel(String realCardName, FileInputStream input) {
        try {
            final int index = allListModel.indexOf(realCardName);
            final MCardCompare card = allListModel.getElementAt(index);
            return card.getModel(input);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * check if the current deck has been modified since it has been saved. If the
     * current deck has been modified, we ask to the user if he want to save it
     * before loading another.
     * 
     * @return true if the user has chosen to save the deck before continuing and
     *         if the save has success. False otherwise
     */
    private boolean verifyModification() {
        if (!modifiedSinceSave) {
            // no modification since the last save
            return true;
        }

        // ask to the user if we save the current deck before loading another
        Object[] options = { "Ok", "No", "Cancel" };
        switch (JOptionPane.showOptionDialog(form,
                "Current deck has been modified, save it before loading another?", "Save changes",
                JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, null)) {
        case 0:
            // YES part
            return saveCurrentDeck();
        case 1:
            // NO part
            return true;
        default:
            // CANCEL part
            return false;
        }
    }

    /**
     * Save the current deck. If the current deck has not been saved once, user
     * have to specify the file's name of this new deck
     * 
     * @return true if the current deck has been correctly saved. false otherwise.
     */
    private boolean saveCurrentDeck() {
        if (isNew || Configuration.getString("decks.deck(0)", "").length() == 0
                && MToolKit.getDeckFile(this, JFileChooser.SAVE_DIALOG) == null) {
            return false;
        }
        return MSaveDeck.saveDeck(Configuration.getString("decks.deck(0)"), rightListModel.getCards(), form);
    }

    /**
     * load the settings of jDecBuilder First byte is the Version Then :
     * 
     * @since 0.2 the MDB file
     * @since 0.2 the last deck file used
     * @since 0.7 the settings are read from .properties file
     */
    private void loadSettings() {
        // read the MDB file
        if (consoleMode || MToolKit.tbsName == null) {
            /*
             * The jDeckbuilder has not been launched from Magic form, or no TBS is
             * currently defined, we load the last used TBS.
             */
            LanguageManager.initLanguageManager(Configuration.getString("language", "auto"));
            optionMenu = new JMenu(LanguageManager.getString("options"));
            optionMenu.setMnemonic('o');
            initAbstractMenu();
            final String tbsName = Configuration.getString("lastTBS");
            if (tbsName != null && tbsName.length() != 0) {
                setMdb(tbsName);
            }
        }
        // read the last used deck file
    }

    /**
     * Invoked when an action occurs.
     * 
     * @param e
     *          attached event
     */
    @SuppressWarnings("unchecked")
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if ("menu_help_about".equals(command)) {
            new About(form).setVisible(true);
        } else if ("menu_db_exit".equals(command)) {
            exitForm();
        } else if ("menu_db_new".equals(command)) {
            if (!verifyModification()) {
                return;
            }
            setTitle("DeckBuilder");
            deckNameTxt.setText("");
            rightListModel.getCards().clear();
            datasets.removeAll();
            modifiedSinceSave = false;
            updateChecker();
            setAsNew();
        } else if ("menu_convert_DCK_MP".equals(command)) {
            /*
             * Convert an entire directory from a format to MP one
             */
            try {
                Converter
                        .convertDCK(MToolKit
                                .showDialogFile("Choose a directory of DCK to convert", 'o', null, FILTER_DECK,
                                        this, JFileChooser.OPEN_DIALOG, JFileChooser.DIRECTORIES_ONLY)
                                .getCanonicalPath());
            } catch (IOException e1) {
                JOptionPane.showMessageDialog(this, "Error occurred reading the specified directory",
                        "File problem", JOptionPane.ERROR_MESSAGE);
            } catch (Exception e1) {
                // cancel of user;
            }
        } else if ("menu_db_constraints".equals(command)) {
            // Show the deck constraints applied on the current TBS
            new DeckRules(this).setVisible(true);
        } else if ("menu_help_help".equals(command)) {
            /*
             * This method is invoked when user has chosen to see the help file. <br>
             * TODO documentation is not yet done for this form
             */
            JOptionPane.showMessageDialog(form, "Sorry, no documentation available for deck builder",
                    "Negative yet implemented", JOptionPane.INFORMATION_MESSAGE);
        } else if ("menu_db_load".equals(command)) {
            if (verifyModification()) {
                String deckFile = MToolKit.getDeckFile(this, JFileChooser.OPEN_DIALOG);
                if (deckFile != null) {
                    loadDeck(deckFile, rightListModel.getCards());
                }
            }
        } else if ("menu_db_saveas".equals(command)) {
            String deckFile = MToolKit.getDeckFile(this, JFileChooser.SAVE_DIALOG);
            if (deckFile != null) {
                MSaveDeck.saveDeck(deckFile, rightListModel.getCards(), form);
                setAsSaved();
            }
        } else if ("menu_db_save".equals(command)) {
            saveCurrentDeck();
            setAsSaved();
        } else {
            // several implemented filters
            final MListModel<MCardCompare> model = (MListModel<MCardCompare>) leftList.getModel();

            final FileInputStream dbStream = MdbLoader.resetMdb();

            final List<MCardCompare> toRemove = new ArrayList<MCardCompare>();

            if ("clear".equals(command)) {
                // Reset the color filters
                for (Component component : toolBar.getComponents()) {
                    if (component instanceof JToggleButton) {
                        ((JToggleButton) component).setSelected(true);
                    }
                }
            }
            model.addAll(model.removedDelegate);

            final int cardType = CardFactory.getIdCard((String) idCardComboBox.getSelectedItem());
            if (cardType != -1) {
                // "All" is not selected in card type filter
                // we remove the cards that don't have the selected card id
                for (MCardCompare cardCompare : model.delegate) {
                    try {
                        final CardModel cardModel = cardCompare.getModel(dbStream);
                        if (!MCard.hasIdCard(cardModel.getIdCard(), cardType)) {
                            toRemove.add(cardCompare);
                        }
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
                model.removeAll(toRemove);
                toRemove.clear();
            }

            // property filter
            // we search for the property value, if it isn't found it's because "All"
            // is selected in the comboBox
            int property = CardFactory.getProperty((String) propertiesComboBox.getSelectedItem());

            if (property != -1) {
                // "All" is not selected in property filter
                // we remove the cards that don't have the selected property
                for (MCardCompare cardCompare : model.delegate) {
                    try {
                        final CardModel cardModel = cardCompare.getModel(dbStream);
                        if (!MCard.hasProperty(cardModel.getProperties(), property)) {
                            toRemove.add(cardCompare);
                        }
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
                model.removeAll(toRemove);
                toRemove.clear();
            }

            // color filters
            for (int i = 1; i < IdCardColors.CARD_COLOR_VALUES.length + 1; i++) {
                final JToggleButton colorButton = (JToggleButton) toolBar.getComponent(i);
                if (!colorButton.isSelected()) {
                    for (MCardCompare cardCompare : model.delegate) {
                        try {
                            final CardModel cardModel = cardCompare.getModel(dbStream);
                            if (i == 1) {
                                if (cardModel.getIdColor() == 0) {
                                    toRemove.add(cardCompare);
                                }
                            } else if ((cardModel.getIdColor()
                                    & IdCardColors.CARD_COLOR_VALUES[i - 1]) == IdCardColors.CARD_COLOR_VALUES[i
                                            - 1]) {
                                toRemove.add(cardCompare);
                            }
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
                    }
                    model.removeAll(toRemove);
                    toRemove.clear();
                }
            }
            leftList.repaint();
            if (!model.isEmpty())
                leftList.setSelectedIndex(0);
        }
    }

    /**
     * Set the current TBS name.
     * 
     * @param tbsName
     *          the TBS to define as current.
     */
    protected void setToolKitMdb(String tbsName) {
        MdbLoader.setToolKitMdb(tbsName);
        readAvailableCards(allListModel);
    }

    public void mouseClicked(MouseEvent e) {
        if (e.getSource() == leftList && e.getClickCount() == 2) {
            refreshAddComponent(true);
        } else if (e.getSource() == removeButton && rightList.getSelectedRow() != -1) {
            removeCardFromDeck();
        } else if (e.getSource() == cardNameTxt) {
            cardNameTxt.selectAll();
        }
    }

    @Override
    public void windowClosing(WindowEvent e) {
        exitForm();
    }

    /**
     * Set the working deck as new.
     */
    protected void setAsNew() {
        isNew = true;
    }

    /**
     * Set the working deck as saved.
     */
    protected void setAsSaved() {
        isNew = false;
        modifiedSinceSave = false;
        String filename = FilenameUtils
                .removeExtension(FilenameUtils.getName(Configuration.getString("decks.deck(0)")));
        setTitle("DeckBuilder : ".concat(filename));
        deckNameTxt.setText(filename);
    }

    /**
     * Are we working with a new deck?
     */
    private boolean isNew = false;

    private final ChartSets datasets;

    private Map<ChartFilter, Map<Integer, IChartKey>> keyMapper;

    private Map<ChartFilter, Map<Integer, Paint>> painterMapper;

    private void initSets() {
        final Map<Integer, Paint> painterColorMapper = new TreeMap<Integer, Paint>();
        for (int index = IdCardColors.CARD_COLOR_VALUES.length; index-- > 0;) {
            painterColorMapper.put(IdCardColors.CARD_COLOR_VALUES[index], IdCardColors.CARD_COLOR[index]);
        }
        painterColorMapper.put(ChartFilter.DEFAULT_KEY, Color.YELLOW.darker().darker());
        painterMapper = new HashMap<ChartFilter, Map<Integer, Paint>>();
        painterMapper.put(ChartFilter.color, painterColorMapper);

        final Map<Integer, IChartKey> keyColorMapper = new TreeMap<Integer, IChartKey>();
        for (int index = 0; index < IdCardColors.CARD_COLOR_VALUES.length; index++) {
            keyColorMapper.put(IdCardColors.CARD_COLOR_VALUES[index],
                    new CardColor(LanguageManager.getString(IdCardColors.CARD_COLOR_NAMES[index]),
                            IdCardColors.CARD_COLOR_VALUES[index]));
        }
        keyColorMapper.put(ChartFilter.DEFAULT_KEY, CardColor.UNKNOW_COLOR);

        final Map<Integer, IChartKey> keyTypeMapper = new TreeMap<Integer, IChartKey>();
        for (int index = 0; index < CardFactory.exportedIdCardNames.length; index++) {
            keyTypeMapper.put(CardFactory.exportedIdCardValues[index],
                    new CardTypes(CardFactory.exportedIdCardNames[index], CardFactory.exportedIdCardValues[index]));
        }
        keyTypeMapper.put(ChartFilter.DEFAULT_KEY, CardTypes.UNKNOW_TYPE);

        keyMapper = new HashMap<ChartFilter, Map<Integer, IChartKey>>();
        keyMapper.put(ChartFilter.color, keyColorMapper);
        keyMapper.put(ChartFilter.type, keyTypeMapper);
        keyMapper.put(ChartFilter.property, new TreeMap<Integer, IChartKey>());
        keyMapper.put(ChartFilter.manacost, new TreeMap<Integer, IChartKey>());
    }

    private Map<Integer, IChartKey> getMapper(ChartFilter filter) {
        return keyMapper.get(filter);
    }

    public List<IChartKey> getKeys(ChartFilter filter) {
        List<IChartKey> list = new ArrayList<IChartKey>(getMapper(filter).values());
        Collections.sort(list);
        return list;
    }

    public Collection<IChartKey> getKeys(CardModel cardModel, ChartFilter filter) {
        Collection<IChartKey> result = new ArrayList<IChartKey>();
        switch (filter) {
        case color:
            int idColor = cardModel.getIdColor();
            if (idColor == 0)
                result.add(getMapper(filter).get(IdCardColors.CARD_COLOR_VALUES[0]));
            else {
                for (IChartKey value : getMapper(filter).values()) {
                    if (value.getIntegerKey() != 0 && MCard.hasIdColor(idColor, value.getIntegerKey())) {
                        result.add(value);
                    }
                }
            }
            break;
        case type:
            int idCard = cardModel.getIdCard();
            for (IChartKey value : getMapper(filter).values()) {
                if (MCard.hasIdCard(idCard, value.getIntegerKey()))
                    result.add(value);
            }
            break;
        case manacost:
            int total = 0;
            for (int index = IdCommonToken.WHITE_MANA + 1; index-- > 0;) {
                total += cardModel.getStaticRegisters()[index];
            }
            result.add(new CardManaCost(total));
            break;
        default:
            // TODO add the the other filters
        }
        if (result.isEmpty()) {
            IChartKey defaultKey = getMapper(filter).get(ChartFilter.DEFAULT_KEY);
            if (defaultKey != null)
                result.add(getMapper(filter).get(ChartFilter.DEFAULT_KEY));
        }
        return result;
    }

    public void tableChanged(TableModelEvent e) {
        if (e.getType() == TableModelEvent.UPDATE)
            rightListModel.getCards().refresh();
    }

    /**
     * Read available cards from file and add them to the specified
     * <code>cardNames</code>
     * 
     * @param cardNames
     *          to this list method add card names
     */
    protected void readAvailableCards(MListModel<MCardCompare> cardNames) {
        final FileInputStream dbStream = MdbLoader.resetMdb();
        listScrollerLeft.setVisible(false);
        try {
            cardNames.clear();
            while (dbStream.available() > 2) {
                // reads card name
                final String cardName = MToolKit.readString(dbStream);

                // reads card offset
                final long offset = MToolKit.readInt24(dbStream);
                final MCardCompare cardCompare = new MCardCompare(cardName, offset);
                cardNames.add(cardCompare);
            }
        } catch (IOException ex2) {
            // Ignore this error
            ex2.printStackTrace();
        } finally {
            listScrollerLeft.setVisible(true);
            cardNames.refresh();
        }
    }

    private static final FileFilterPlus FILTER_DECK = new FileFilterPlus("dck", "DCK : Shandalar deck file");

    /**
     * The left list model containing all cards.
     */
    protected final MListModel<MCardCompare> allListModel;

    /**
     * The right table model containing the current deck cards.
     */
    protected final MCardTableModel rightListModel;

    private static boolean modifiedSinceSave = false;

    private final JButton addButton;

    private final JButton removeButton;

    /**
     * The right list : cards in the deck.
     */
    private final JTable rightList;

    /**
     * The left list : available cards.
     */
    protected final JList leftList;

    private final JTextField cardNameTxt;

    /**
     * Add text
     */
    private final JTextField addQtyTxt;

    /**
     * Remove text
     */
    private final JTextField removeQtyTxt;

    /**
     * The label displayed above the card picture
     */
    private final JLabel cardPictureNameTxt;

    /**
     * indicates if deck builder was launched from console
     */
    public static boolean consoleMode = false;

    /**
     * The toolBar containing some filters and card type combobox.
     */
    private final JToolBar toolBar;

    private final JComboBox idCardComboBox;

    private final JComboBox propertiesComboBox;

    private final JLabel oracleText;

    private final ConstraintsChecker constraintsChecker;

    /**
     * The deck title
     */
    private final JTextField deckNameTxt;

    /**
     * The unique instance of this form.
     */
    public static DeckBuilder form;
}