View Javadoc

1   /*
2    *   Firemox is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it 
6    * under the terms of the GNU General Public License as published by the Free 
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT 
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along  
16   * with this program; if not, write to the Free Software Foundation, Inc., 
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  package net.sf.firemox;
20  
21  import java.awt.BorderLayout;
22  import java.awt.Color;
23  import java.awt.Component;
24  import java.awt.Dimension;
25  import java.awt.Paint;
26  import java.awt.event.ActionEvent;
27  import java.awt.event.InputEvent;
28  import java.awt.event.KeyEvent;
29  import java.awt.event.MouseEvent;
30  import java.awt.event.WindowEvent;
31  import java.io.FileInputStream;
32  import java.io.IOException;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Collection;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.TreeMap;
41  
42  import javax.swing.BorderFactory;
43  import javax.swing.Box;
44  import javax.swing.BoxLayout;
45  import javax.swing.JButton;
46  import javax.swing.JComboBox;
47  import javax.swing.JComponent;
48  import javax.swing.JDialog;
49  import javax.swing.JFileChooser;
50  import javax.swing.JFrame;
51  import javax.swing.JLabel;
52  import javax.swing.JList;
53  import javax.swing.JMenu;
54  import javax.swing.JMenuBar;
55  import javax.swing.JMenuItem;
56  import javax.swing.JOptionPane;
57  import javax.swing.JPanel;
58  import javax.swing.JScrollPane;
59  import javax.swing.JSeparator;
60  import javax.swing.JTabbedPane;
61  import javax.swing.JTable;
62  import javax.swing.JTextField;
63  import javax.swing.JToggleButton;
64  import javax.swing.JToolBar;
65  import javax.swing.KeyStroke;
66  import javax.swing.ListSelectionModel;
67  import javax.swing.ScrollPaneConstants;
68  import javax.swing.SwingConstants;
69  import javax.swing.Timer;
70  import javax.swing.border.EtchedBorder;
71  import javax.swing.event.ListSelectionEvent;
72  import javax.swing.event.ListSelectionListener;
73  import javax.swing.event.TableModelEvent;
74  import javax.swing.event.TableModelListener;
75  
76  import net.sf.firemox.chart.CardColor;
77  import net.sf.firemox.chart.CardManaCost;
78  import net.sf.firemox.chart.CardTypes;
79  import net.sf.firemox.chart.ChartFilter;
80  import net.sf.firemox.chart.ChartSets;
81  import net.sf.firemox.chart.IChartKey;
82  import net.sf.firemox.chart.IDataProvider;
83  import net.sf.firemox.chart.datasets.Dataset;
84  import net.sf.firemox.clickable.target.card.CardFactory;
85  import net.sf.firemox.clickable.target.card.CardModel;
86  import net.sf.firemox.clickable.target.card.MCard;
87  import net.sf.firemox.deckbuilder.CardLoader;
88  import net.sf.firemox.deckbuilder.CardView;
89  import net.sf.firemox.deckbuilder.ConstraintsChecker;
90  import net.sf.firemox.deckbuilder.Deck;
91  import net.sf.firemox.deckbuilder.DeckConstraints;
92  import net.sf.firemox.deckbuilder.DeckReader;
93  import net.sf.firemox.deckbuilder.DeckRules;
94  import net.sf.firemox.deckbuilder.MdbLoader;
95  import net.sf.firemox.token.IdCardColors;
96  import net.sf.firemox.token.IdCommonToken;
97  import net.sf.firemox.token.IdConst;
98  import net.sf.firemox.tools.Configuration;
99  import net.sf.firemox.tools.Converter;
100 import net.sf.firemox.tools.FileFilterPlus;
101 import net.sf.firemox.tools.MCardCompare;
102 import net.sf.firemox.tools.MSaveDeck;
103 import net.sf.firemox.tools.MToolKit;
104 import net.sf.firemox.tools.Picture;
105 import net.sf.firemox.ui.HireListener;
106 import net.sf.firemox.ui.MCardTableModel;
107 import net.sf.firemox.ui.MListModel;
108 import net.sf.firemox.ui.RefreshableAdd;
109 import net.sf.firemox.ui.TimerGlassPane;
110 import net.sf.firemox.ui.UIHelper;
111 import net.sf.firemox.ui.component.ThreadSafeJList;
112 import net.sf.firemox.ui.i18n.LanguageManager;
113 import net.sf.firemox.ui.wizard.About;
114 
115 import org.apache.commons.io.FilenameUtils;
116 import org.apache.commons.lang.ArrayUtils;
117 import org.jfree.chart.ChartPanel;
118 import org.jfree.chart.JFreeChart;
119 
120 /***
121  * This class is a deck builder compatible with Firemox deck format. Can
122  * also export/import other formats.
123  * 
124  * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
125  * @author <a href="mailto:goldeneyemdk@users.sourceforge.net">Sebastien Genete
126  *         </a>
127  * @author Jan Blaha for deck loader and statistics
128  * @since 0.2 2003/12/06, settings are loaded.
129  * @since 0.2 you can load a deck file.
130  * @since 0.2 you can load a specified MDB file (is MagicProject card list
131  *        file).
132  * @since 0.3 2003/12/12, you can save a deck file.
133  * @since 0.3 new deck feature.
134  * @since 0.4 sort MDB file feature for optimized load.
135  * @since 0.4 deck are saved with alphabetically sort.
136  * @since 0.5 statistics feature added.
137  * @since 0.81 deck converter added.
138  * @since 0.83 cleaned code, deck constraints form added.
139  * @since 0.93 pie chart added, color filtering
140  * @since 0.94 use of JTable instead of JList
141  */
142 public final class DeckBuilder extends AbstractMainForm implements
143 		ListSelectionListener, RefreshableAdd, IDataProvider, TableModelListener {
144 
145 	private static final int WINDOW_WIDTH = 700;
146 
147 	private static final int WINDOW_HEIGHT = 700;
148 
149 	/***
150 	 * Card loader bar
151 	 */
152 	protected CardLoader cardLoader;
153 
154 	/***
155 	 * Load timer
156 	 */
157 	protected Timer timer;
158 
159 	private final TimerGlassPane timerPanel;
160 
161 	private JComponent listScrollerLeft;
162 
163 	/***
164 	 * Creates new form DeckBuilder
165 	 */
166 	private DeckBuilder() {
167 		super("DeckBuilder");
168 		form = this;
169 		timerPanel = new TimerGlassPane();
170 		cardLoader = new CardLoader(timerPanel);
171 		timer = new Timer(200, cardLoader);
172 		setGlassPane(timerPanel);
173 		try {
174 			setIconImage(Picture.loadImage(IdConst.IMAGES_DIR + "deckbuilder.gif"));
175 		} catch (Exception e) {
176 			// IGNORING
177 		}
178 
179 		// Load settings
180 		loadSettings();
181 
182 		// Initialize components
183 		final JMenuItem newItem = UIHelper.buildMenu("menu_db_new", 'n', this);
184 		newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
185 				InputEvent.CTRL_MASK));
186 
187 		final JMenuItem loadItem = UIHelper.buildMenu("menu_db_load", 'o', this);
188 		loadItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
189 				InputEvent.CTRL_MASK));
190 
191 		final JMenuItem saveAsItem = UIHelper
192 				.buildMenu("menu_db_saveas", 'a', this);
193 		saveAsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F12, 0));
194 
195 		final JMenuItem saveItem = UIHelper.buildMenu("menu_db_save", 's', this);
196 		saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
197 				InputEvent.CTRL_MASK));
198 
199 		final JMenuItem quitItem = UIHelper.buildMenu("menu_db_exit", this);
200 		quitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4,
201 				InputEvent.ALT_MASK));
202 
203 		final JMenuItem deckConstraintsItem = UIHelper.buildMenu(
204 				"menu_db_constraints", 'c', this);
205 		deckConstraintsItem.setAccelerator(KeyStroke
206 				.getKeyStroke(KeyEvent.VK_F3, 0));
207 
208 		final JMenuItem aboutItem = UIHelper
209 				.buildMenu("menu_help_about", 'a', this);
210 		aboutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1,
211 				InputEvent.SHIFT_MASK));
212 
213 		final JMenuItem convertDCK = UIHelper
214 				.buildMenu("menu_convert_DCK_MP", this);
215 
216 		final JMenu mainMenu = UIHelper.buildMenu("menu_file");
217 		mainMenu.add(newItem);
218 		mainMenu.add(loadItem);
219 		mainMenu.add(saveAsItem);
220 		mainMenu.add(saveItem);
221 		mainMenu.add(new JSeparator());
222 		mainMenu.add(quitItem);
223 
224 		super.optionMenu = new JMenu("Options");
225 
226 		final JMenu convertMenu = UIHelper.buildMenu("menu_convert");
227 		convertMenu.add(convertDCK);
228 
229 		final JMenuItem helpItem = UIHelper.buildMenu("menu_help_help", 'h', this);
230 		helpItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
231 
232 		final JMenu helpMenu = new JMenu("?");
233 		helpMenu.add(helpItem);
234 		helpMenu.add(deckConstraintsItem);
235 		helpMenu.add(aboutItem);
236 
237 		final JMenuBar menuBar = new JMenuBar();
238 		menuBar.add(mainMenu);
239 		initAbstractMenu();
240 		menuBar.add(optionMenu);
241 		menuBar.add(convertMenu);
242 		menuBar.add(helpMenu);
243 		setJMenuBar(menuBar);
244 		addWindowListener(this);
245 
246 		// Build the panel containing amount of available cards
247 		final JLabel amountLeft = new JLabel("<html>0/?", SwingConstants.RIGHT);
248 
249 		// Build the left list
250 		allListModel = new MListModel<MCardCompare>(amountLeft, false);
251 		leftList = new ThreadSafeJList(allListModel);
252 		leftList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
253 		leftList.setLayoutOrientation(JList.VERTICAL);
254 		leftList.getSelectionModel().addListSelectionListener(this);
255 		leftList.addMouseListener(this);
256 		leftList.setVisibleRowCount(10);
257 
258 		// Initialize the text field containing the amount to add
259 		addQtyTxt = new JTextField("1");
260 
261 		// Build the "Add" button
262 		addButton = new JButton(LanguageManager.getString("db_add"));
263 		addButton.setMnemonic('a');
264 		addButton.setEnabled(false);
265 
266 		// Build the panel containing : "Add" amount and "Add" button
267 		final Box addPanel = Box.createHorizontalBox();
268 		addPanel.add(addButton);
269 		addPanel.add(addQtyTxt);
270 		addPanel.setMaximumSize(new Dimension(32010, 26));
271 
272 		// Build the panel containing the selected card name
273 		cardNameTxt = new JTextField();
274 		new HireListener(cardNameTxt, addButton, this, leftList);
275 
276 		final JLabel searchLabel = new JLabel(LanguageManager
277 				.getString("db_search")
278 				+ " : ");
279 		searchLabel.setLabelFor(cardNameTxt);
280 
281 		// Build the panel containing search label and card name text field
282 		final Box searchPanel = Box.createHorizontalBox();
283 		searchPanel.add(searchLabel);
284 		searchPanel.add(cardNameTxt);
285 		searchPanel.setMaximumSize(new Dimension(32010, 26));
286 
287 		listScrollerLeft = new JScrollPane(leftList);
288 		MToolKit.addOverlay(listScrollerLeft);
289 
290 		// Build the left panel containing : list, available amount, "Add" panel
291 		final JPanel srcPanel = new JPanel(null);
292 		srcPanel.add(searchPanel);
293 		srcPanel.add(listScrollerLeft);
294 		srcPanel.add(amountLeft);
295 		srcPanel.add(addPanel);
296 		srcPanel.setMinimumSize(new Dimension(220, 200));
297 		srcPanel.setLayout(new BoxLayout(srcPanel, BoxLayout.Y_AXIS));
298 
299 		// Initialize constraints
300 		constraintsChecker = new ConstraintsChecker();
301 		constraintsChecker.setBorder(new EtchedBorder());
302 		final JScrollPane constraintsCheckerScroll = new JScrollPane(
303 				constraintsChecker);
304 		MToolKit.addOverlay(constraintsCheckerScroll);
305 
306 		// create a pane with the oracle text for the present card
307 		oracleText = new JLabel();
308 		oracleText.setPreferredSize(new Dimension(180, 200));
309 		oracleText.setVerticalAlignment(SwingConstants.TOP);
310 
311 		final JScrollPane oracle = new JScrollPane(oracleText,
312 				ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
313 				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
314 		MToolKit.addOverlay(oracle);
315 
316 		// build some Pie Charts and a panel to display it
317 		initSets();
318 		datasets = new ChartSets();
319 		final JTabbedPane tabbedPane = new JTabbedPane();
320 		for (ChartFilter filter : ChartFilter.values()) {
321 			final Dataset dataSet = filter.createDataSet(this);
322 			final JFreeChart chart = new JFreeChart(null, null, filter.createPlot(
323 					dataSet, painterMapper.get(filter)), false);
324 			datasets.addDataSet(filter, dataSet);
325 			ChartPanel pieChartPanel = new ChartPanel(chart, true);
326 			tabbedPane.add(pieChartPanel, filter.getTitle());
327 		}
328 		// add the Constraints scroll panel and Oracle text Pane to the tabbedPane
329 		tabbedPane.add(constraintsCheckerScroll, LanguageManager
330 				.getString("db_constraints"));
331 		tabbedPane.add(oracle, LanguageManager.getString("db_text"));
332 		tabbedPane.setSelectedComponent(oracle);
333 
334 		// The toollBar for color filtering
335 		toolBar = new JToolBar();
336 		toolBar.setFloatable(false);
337 		final JButton clearButton = UIHelper.buildButton("clear");
338 		clearButton.addActionListener(this);
339 		toolBar.add(clearButton);
340 		final JToggleButton toggleColorlessButton = new JToggleButton(UIHelper
341 				.getTbsIcon("mana/colorless/small/" + MdbLoader.unknownSmlMana), true);
342 		toggleColorlessButton.setActionCommand("0");
343 		toggleColorlessButton.addActionListener(this);
344 		toolBar.add(toggleColorlessButton);
345 		for (int index = 1; index < IdCardColors.CARD_COLOR_NAMES.length; index++) {
346 			final JToggleButton toggleButton = new JToggleButton(
347 					UIHelper.getTbsIcon("mana/colored/small/"
348 							+ MdbLoader.coloredSmlManas[index]), true);
349 			toggleButton.setActionCommand(String.valueOf(index));
350 			toggleButton.addActionListener(this);
351 			toolBar.add(toggleButton);
352 		}
353 
354 		// sorted card type combobox creation
355 		final List<String> idCards = new ArrayList<String>(Arrays
356 				.asList(CardFactory.exportedIdCardNames));
357 		Collections.sort(idCards);
358 		final Object[] cardTypes = ArrayUtils.addAll(new String[] { LanguageManager
359 				.getString("db_types.any") }, idCards.toArray());
360 		idCardComboBox = new JComboBox(cardTypes);
361 		idCardComboBox.setSelectedIndex(0);
362 		idCardComboBox.addActionListener(this);
363 		idCardComboBox.setActionCommand("cardTypeFilter");
364 
365 		// sorted card properties combobox creation
366 		final List<String> properties = new ArrayList<String>(CardFactory
367 				.getPropertiesName(DeckConstraints.getMinProperty(), DeckConstraints
368 						.getMaxProperty()));
369 		Collections.sort(properties);
370 		final Object[] cardProperties = ArrayUtils.addAll(
371 				new String[] { LanguageManager.getString("db_properties.any") },
372 				properties.toArray());
373 		propertiesComboBox = new JComboBox(cardProperties);
374 		propertiesComboBox.setSelectedIndex(0);
375 		propertiesComboBox.addActionListener(this);
376 		propertiesComboBox.setActionCommand("propertyFilter");
377 
378 		final JLabel colors = new JLabel(" " + LanguageManager.getString("colors")
379 				+ " : ");
380 		final JLabel types = new JLabel(" " + LanguageManager.getString("types")
381 				+ " : ");
382 		final JLabel property = new JLabel(" "
383 				+ LanguageManager.getString("properties") + " : ");
384 
385 		// filter Panel with colors toolBar and card type combobox
386 		final Box filterPanel = Box.createHorizontalBox();
387 		filterPanel.add(colors);
388 		filterPanel.add(toolBar);
389 		filterPanel.add(types);
390 		filterPanel.add(idCardComboBox);
391 		filterPanel.add(property);
392 		filterPanel.add(propertiesComboBox);
393 
394 		getContentPane().add(filterPanel, BorderLayout.NORTH);
395 
396 		// Destination section :
397 
398 		// Build the panel containing amount of available cards
399 		final JLabel rightAmount = new JLabel("0/?", SwingConstants.RIGHT);
400 		rightAmount.setMaximumSize(new Dimension(220, 26));
401 
402 		// Build the right list
403 		rightListModel = new MCardTableModel(new MListModel<MCardCompare>(
404 				rightAmount, true));
405 		rightListModel.addTableModelListener(this);
406 		rightList = new JTable(rightListModel);
407 		rightList.setShowGrid(false);
408 		rightList.setTableHeader(null);
409 		rightList.getSelectionModel().addListSelectionListener(this);
410 		rightList.getColumnModel().getColumn(0).setMaxWidth(25);
411 		rightList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
412 
413 		// Build the panel containing the selected deck
414 		deckNameTxt = new JTextField("loading...");
415 		deckNameTxt.setEditable(false);
416 		deckNameTxt.setBorder(null);
417 		final JLabel deckLabel = new JLabel(LanguageManager.getString("db_deck")
418 				+ " : ");
419 		deckLabel.setLabelFor(deckNameTxt);
420 		final Box deckNamePanel = Box.createHorizontalBox();
421 		deckNamePanel.add(deckLabel);
422 		deckNamePanel.add(deckNameTxt);
423 		deckNamePanel.setMaximumSize(new Dimension(220, 26));
424 
425 		// Initialize the text field containing the amount to remove
426 		removeQtyTxt = new JTextField("1");
427 
428 		// Build the "Remove" button
429 		removeButton = new JButton(LanguageManager.getString("db_remove"));
430 		removeButton.setMnemonic('r');
431 		removeButton.addMouseListener(this);
432 		removeButton.setEnabled(false);
433 
434 		// Build the panel containing : "Remove" amount and "Remove" button
435 		final Box removePanel = Box.createHorizontalBox();
436 		removePanel.add(removeButton);
437 		removePanel.add(removeQtyTxt);
438 		removePanel.setMaximumSize(new Dimension(220, 26));
439 
440 		// Build the right panel containing : list, available amount, constraints
441 		final JScrollPane deskListScroller = new JScrollPane(rightList);
442 		MToolKit.addOverlay(deskListScroller);
443 		deskListScroller.setBorder(BorderFactory.createLineBorder(Color.GRAY));
444 		deskListScroller.setMinimumSize(new Dimension(220, 200));
445 		deskListScroller.setMaximumSize(new Dimension(220, 32000));
446 
447 		final Box destPanel = Box.createVerticalBox();
448 		destPanel.add(deckNamePanel);
449 		destPanel.add(deskListScroller);
450 		destPanel.add(rightAmount);
451 		destPanel.add(removePanel);
452 		destPanel.setMinimumSize(new Dimension(220, 200));
453 		destPanel.setMaximumSize(new Dimension(220, 32000));
454 
455 		// Build the panel containing the name of card in picture
456 		cardPictureNameTxt = new JLabel("<html><i>no selected card</i>");
457 		final Box cardPictureNamePanel = Box.createHorizontalBox();
458 		cardPictureNamePanel.add(cardPictureNameTxt);
459 		cardPictureNamePanel.setMaximumSize(new Dimension(32010, 26));
460 
461 		// Group the detail panels
462 		final JPanel viewCard = new JPanel(null);
463 		viewCard.add(cardPictureNamePanel);
464 		viewCard.add(CardView.getInstance());
465 		viewCard.add(tabbedPane);
466 		viewCard.setLayout(new BoxLayout(viewCard, BoxLayout.Y_AXIS));
467 
468 		final Box mainPanel = Box.createHorizontalBox();
469 		mainPanel.add(destPanel);
470 		mainPanel.add(viewCard);
471 
472 		// Add the main panel
473 		getContentPane().add(srcPanel, BorderLayout.WEST);
474 		getContentPane().add(mainPanel, BorderLayout.CENTER);
475 
476 		// Size this frame
477 		getRootPane().setPreferredSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
478 		getRootPane().setMinimumSize(getRootPane().getPreferredSize());
479 		pack();
480 	}
481 
482 	/***
483 	 * Load a given deck file into the given list.
484 	 * 
485 	 * @param fileName
486 	 *          deck file name.
487 	 * @param names
488 	 *          loaded cards to complete.
489 	 */
490 	protected void loadDeck(String fileName, MListModel<MCardCompare> names) {
491 		// empty file name or deck?
492 		if (fileName == null || fileName.length() == 0) {
493 			return;
494 		}
495 
496 		// Append the deck name
497 		final String deckName = FilenameUtils.removeExtension(FilenameUtils
498 				.getName(fileName));
499 		setTitle("DeckBuilder : " + deckName);
500 		deckNameTxt.setText(deckName);
501 
502 		names.clear();
503 		datasets.removeAll();
504 		try {
505 			Deck currentDeck = DeckReader.getDeck(this, fileName);
506 			if (currentDeck == null) {
507 				// Deck loading failure
508 				return;
509 			}
510 
511 			// Validate this deck
512 			DeckReader.validateDeck(this, currentDeck, (String) null);
513 
514 			// Update the card models of resolved cards
515 			final FileInputStream dbStream = MdbLoader.resetMdb();
516 			for (MCardCompare cardCompare : currentDeck.getCards()) {
517 				names.add(cardCompare);
518 				datasets.addCard(cardCompare.getModel(dbStream), cardCompare
519 						.getAmount());
520 			}
521 			constraintsChecker.setDeck(currentDeck);
522 		} catch (IOException e) {
523 			e.printStackTrace();
524 		} finally {
525 			updateChecker();
526 			setAsSaved();
527 		}
528 	}
529 
530 	private void setCard(MCardCompare card) {
531 		CardView.getInstance().setCard(card);
532 		cardPictureNameTxt.setText(card.getName());
533 		FileInputStream dbStream = MdbLoader.resetMdb();
534 		try {
535 			oracleText.setText("<html>Oracle Rules:<br><br>"
536 					+ ((MCard) card.getCard(dbStream)).getDatabase().getProperty(
537 							"card.text"));
538 		} catch (IOException e) {
539 			// ignore
540 			e.printStackTrace();
541 		}
542 	}
543 
544 	public void valueChanged(ListSelectionEvent e) {
545 		if (!e.getValueIsAdjusting()) {
546 			if (e.getSource() == leftList.getSelectionModel()) {
547 				if (leftList.getSelectedIndex() == -1 || allListModel.isEmpty()) {
548 					// No selection, disable fire button.
549 					addButton.setEnabled(false);
550 				} else {
551 					// Selection, enable the fire button.
552 					addButton.setEnabled(true);
553 
554 					// Update the views
555 					final MCardCompare selectedCard = (MCardCompare) leftList
556 							.getSelectedValue();
557 					cardNameTxt.setText(selectedCard.getName());
558 					setCard(selectedCard);
559 				}
560 			} else if (e.getSource() == rightList.getSelectionModel()) {
561 				if (rightList.getSelectedRow() == -1) {
562 					// No selection, disable fire button.
563 					removeButton.setEnabled(false);
564 				} else {
565 					// Selection, enable the fire button.
566 					removeButton.setEnabled(true);
567 
568 					// Update the views
569 					setCard(rightListModel.getCards().getElementAt(
570 							rightList.getSelectedRow()));
571 				}
572 			}
573 		}
574 	}
575 
576 	/***
577 	 * Exit the Application
578 	 */
579 	private void exitForm() {
580 		verifyModification();
581 		if (consoleMode) {
582 			System.exit(0);
583 		} else {
584 			setVisible(false);
585 		}
586 	}
587 
588 	/***
589 	 * Load the deck builder from the command line.
590 	 * 
591 	 * @param args
592 	 *          the command line arguments
593 	 */
594 	public static void main(String[] args) {
595 		consoleMode = true;
596 		JFrame.setDefaultLookAndFeelDecorated(true);
597 		JDialog.setDefaultLookAndFeelDecorated(true);
598 		autoLoad();
599 	}
600 
601 	/***
602 	 * Load the deck builder from Magic class.
603 	 */
604 	public static void loadFromMagic() {
605 		consoleMode = false;
606 		autoLoad();
607 	}
608 
609 	/***
610 	 * Load the deck builder
611 	 */
612 	private static void autoLoad() {
613 		if (form == null) {
614 			form = new DeckBuilder();
615 			form.setLocation(
616 					(form.getToolkit().getScreenSize().width - WINDOW_WIDTH) / 2, (form
617 							.getToolkit().getScreenSize().height - WINDOW_HEIGHT) / 2);
618 		}
619 		form.setVisible(true);
620 		if (form.allListModel.isEmpty()) {
621 			form.timerPanel.setVisible(true);
622 			form.timer.start();
623 			new Thread(
624 			/***
625 			 * This class refresh the loading bar
626 			 */
627 			new Runnable() {
628 				public void run() {
629 					// Populate available cards and fill them into the left list
630 					form.readAvailableCards(form.allListModel);
631 
632 					// Load the last deck file
633 					if (Configuration.getString("decks.deck(0)", "").length() > 0) {
634 						form.loadDeck(Configuration.getString("decks.deck(0)"),
635 								form.rightListModel.getCards());
636 						form.rightListModel.refresh();
637 					} else {
638 						form.setAsNew();
639 					}
640 
641 					form.timer.stop();
642 					form.cardLoader.resetCounter();
643 				}
644 			}).start();
645 		}
646 	}
647 
648 	/***
649 	 * Adding a card to the deck
650 	 * 
651 	 * @param unitary
652 	 */
653 	public void refreshAddComponent(boolean unitary) {
654 
655 		if (leftList.isSelectionEmpty())
656 			return;
657 
658 		String name = leftList.getSelectedValue().toString();
659 		int qty = 1;
660 		if (!unitary) {
661 			try {
662 				qty = Integer.parseInt(addQtyTxt.getText());
663 			} catch (Exception e1) {
664 				JOptionPane.showMessageDialog(form, "Malformed integer for quantity",
665 						"Internal Error", JOptionPane.ERROR_MESSAGE);
666 				return;
667 			}
668 		}
669 		// User didn't type in a unique name...
670 		if (name.length() == 0) {
671 			cardNameTxt.requestFocusInWindow();
672 			return;
673 		}
674 
675 		int index = rightListModel.getCards().indexOf(name);
676 		final MCardCompare card;
677 		if (index == -1) {
678 			// no selection, so insert at beginning
679 			int allIndex = allListModel.indexOf(name);
680 			assert allIndex != -1;
681 			card = allListModel.getElementAt(allIndex).clone();
682 			card.add(qty);
683 			rightListModel.getCards().insertElementAt(card, 0);
684 			rightListModel.fireTableRowsInserted(0, 0);
685 
686 			// Reset the text field.
687 			cardNameTxt.requestFocusInWindow();
688 			cardNameTxt.setText("");
689 		} else { // add after the selected item
690 			rightListModel.getCards().getElementAt(index).add(qty);
691 			rightListModel.fireTableCellUpdated(index, 0);
692 		}
693 
694 		// Update the card models of resolved cards
695 		final FileInputStream dbStream = MdbLoader.resetMdb();
696 		datasets.addCard(getCardModel(name, dbStream), qty);
697 
698 		// deck list has been modified
699 		modifiedSinceSave = true;
700 	}
701 
702 	/***
703 	 * removes cards from current deck according to the remove panel
704 	 */
705 	private void removeCardFromDeck() {
706 
707 		final MCardCompare card = rightListModel.getCards().getElementAt(
708 				rightList.getSelectedRow());
709 
710 		int qty = 1;
711 
712 		try {
713 			qty = Integer.parseInt(removeQtyTxt.getText());
714 		} catch (Exception e1) {
715 			JOptionPane.showMessageDialog(form, "Malformed integer for quantity",
716 					"Internal Error", JOptionPane.ERROR_MESSAGE);
717 			return;
718 		}
719 
720 		card.remove(qty);
721 
722 		if (card.getAmount() <= 0) {
723 			rightListModel.getCards().remove(rightList.getSelectedRow());
724 			rightListModel.fireTableRowsDeleted(0, rightList.getRowCount());
725 		} else
726 			rightListModel.fireTableCellUpdated(rightListModel.getCards().indexOf(
727 					card.getName()), 0);
728 
729 		final FileInputStream dbStream = MdbLoader.resetMdb();
730 		datasets.removeCard(getCardModel(card.getName(), dbStream), qty);
731 		modifiedSinceSave = true;
732 
733 	}
734 
735 	/***
736 	 * Update the checker and repaint the right list.
737 	 */
738 	private void updateChecker() {
739 		constraintsChecker.updateCheckers();
740 		rightList.repaint();
741 	}
742 
743 	/***
744 	 * Return the associated cardModel to the given real card name. The card is
745 	 * searched in the input starting from a given offset.
746 	 * 
747 	 * @param realCardName
748 	 *          the card name as written in the MDB stream.
749 	 * @param input
750 	 *          the input stream of current MDB.
751 	 * @return the corresponding CardModel instance, or null if the card has not
752 	 *         been matched.
753 	 */
754 	private CardModel getCardModel(String realCardName, FileInputStream input) {
755 		try {
756 			final int index = allListModel.indexOf(realCardName);
757 			final MCardCompare card = allListModel.getElementAt(index);
758 			return card.getModel(input);
759 		} catch (IOException e) {
760 			return null;
761 		}
762 	}
763 
764 	/***
765 	 * check if the current deck has been modified since it has been saved. If the
766 	 * current deck has been modified, we ask to the user if he want to save it
767 	 * before loading another.
768 	 * 
769 	 * @return true if the user has chosen to save the deck before continuing and
770 	 *         if the save has success. False otherwise
771 	 */
772 	private boolean verifyModification() {
773 		if (!modifiedSinceSave) {
774 			// no modification since the last save
775 			return true;
776 		}
777 
778 		// ask to the user if we save the current deck before loading another
779 		Object[] options = { "Ok", "No", "Cancel" };
780 		switch (JOptionPane.showOptionDialog(form,
781 				"Current deck has been modified, save it before loading another?",
782 				"Save changes", JOptionPane.YES_NO_CANCEL_OPTION,
783 				JOptionPane.WARNING_MESSAGE, null, options, null)) {
784 		case 0:
785 			// YES part
786 			return saveCurrentDeck();
787 		case 1:
788 			// NO part
789 			return true;
790 		default:
791 			// CANCEL part
792 			return false;
793 		}
794 	}
795 
796 	/***
797 	 * Save the current deck. If the current deck has not been saved once, user
798 	 * have to specify the file's name of this new deck
799 	 * 
800 	 * @return true if the current deck has been correctly saved. false otherwise.
801 	 */
802 	private boolean saveCurrentDeck() {
803 		if (isNew || Configuration.getString("decks.deck(0)", "").length() == 0
804 				&& MToolKit.getDeckFile(this, JFileChooser.SAVE_DIALOG) == null) {
805 			return false;
806 		}
807 		return MSaveDeck.saveDeck(Configuration.getString("decks.deck(0)"),
808 				rightListModel.getCards(), form);
809 	}
810 
811 	/***
812 	 * load the settings of jDecBuilder First byte is the Version Then :
813 	 * 
814 	 * @since 0.2 the MDB file
815 	 * @since 0.2 the last deck file used
816 	 * @since 0.7 the settings are read from .properties file
817 	 */
818 	private void loadSettings() {
819 		// read the MDB file
820 		if (consoleMode || MToolKit.tbsName == null) {
821 			/*
822 			 * The jDeckbuilder has not been launched from Magic form, or no TBS is
823 			 * currently defined, we load the last used TBS.
824 			 */
825 			LanguageManager.initLanguageManager(Configuration.getString("language",
826 					"auto"));
827 			optionMenu = new JMenu(LanguageManager.getString("options"));
828 			optionMenu.setMnemonic('o');
829 			initAbstractMenu();
830 			final String tbsName = Configuration.getString("lastTBS");
831 			if (tbsName != null && tbsName.length() != 0) {
832 				setMdb(tbsName);
833 			}
834 		}
835 		// read the last used deck file
836 	}
837 
838 	/***
839 	 * Invoked when an action occurs.
840 	 * 
841 	 * @param e
842 	 *          attached event
843 	 */
844 	@SuppressWarnings("unchecked")
845 	public void actionPerformed(ActionEvent e) {
846 		String command = e.getActionCommand();
847 		if ("menu_help_about".equals(command)) {
848 			new About(form).setVisible(true);
849 		} else if ("menu_db_exit".equals(command)) {
850 			exitForm();
851 		} else if ("menu_db_new".equals(command)) {
852 			if (!verifyModification()) {
853 				return;
854 			}
855 			setTitle("DeckBuilder");
856 			deckNameTxt.setText("");
857 			rightListModel.getCards().clear();
858 			datasets.removeAll();
859 			modifiedSinceSave = false;
860 			updateChecker();
861 			setAsNew();
862 		} else if ("menu_convert_DCK_MP".equals(command)) {
863 			/*
864 			 * Convert an entire directory from a format to MP one
865 			 */
866 			try {
867 				Converter.convertDCK(MToolKit.showDialogFile(
868 						"Choose a directory of DCK to convert", 'o', null, FILTER_DECK,
869 						this, JFileChooser.OPEN_DIALOG, JFileChooser.DIRECTORIES_ONLY)
870 						.getCanonicalPath());
871 			} catch (IOException e1) {
872 				JOptionPane.showMessageDialog(this,
873 						"Error occurred reading the specified directory", "File problem",
874 						JOptionPane.ERROR_MESSAGE);
875 			} catch (Exception e1) {
876 				// cancel of user;
877 			}
878 		} else if ("menu_db_constraints".equals(command)) {
879 			// Show the deck constraints applied on the current TBS
880 			new DeckRules(this).setVisible(true);
881 		} else if ("menu_help_help".equals(command)) {
882 			/*
883 			 * This method is invoked when user has chosen to see the help file. <br>
884 			 * TODO documentation is not yet done for this form
885 			 */
886 			JOptionPane.showMessageDialog(form,
887 					"Sorry, no documentation available for deck builder",
888 					"Negative yet implemented", JOptionPane.INFORMATION_MESSAGE);
889 		} else if ("menu_db_load".equals(command)) {
890 			if (verifyModification()) {
891 				String deckFile = MToolKit.getDeckFile(this, JFileChooser.OPEN_DIALOG);
892 				if (deckFile != null) {
893 					loadDeck(deckFile, rightListModel.getCards());
894 				}
895 			}
896 		} else if ("menu_db_saveas".equals(command)) {
897 			String deckFile = MToolKit.getDeckFile(this, JFileChooser.SAVE_DIALOG);
898 			if (deckFile != null) {
899 				MSaveDeck.saveDeck(deckFile, rightListModel.getCards(), form);
900 				setAsSaved();
901 			}
902 		} else if ("menu_db_save".equals(command)) {
903 			saveCurrentDeck();
904 			setAsSaved();
905 		} else {
906 			// several implemented filters
907 			final MListModel<MCardCompare> model = (MListModel<MCardCompare>) leftList
908 					.getModel();
909 
910 			final FileInputStream dbStream = MdbLoader.resetMdb();
911 
912 			final List<MCardCompare> toRemove = new ArrayList<MCardCompare>();
913 
914 			if ("clear".equals(command)) {
915 				// Reset the color filters
916 				for (Component component : toolBar.getComponents()) {
917 					if (component instanceof JToggleButton) {
918 						((JToggleButton) component).setSelected(true);
919 					}
920 				}
921 			}
922 			model.addAll(model.removedDelegate);
923 
924 			final int cardType = CardFactory.getIdCard((String) idCardComboBox
925 					.getSelectedItem());
926 			if (cardType != -1) {
927 				// "All" is not selected in card type filter
928 				// we remove the cards that don't have the selected card id
929 				for (MCardCompare cardCompare : model.delegate) {
930 					try {
931 						final CardModel cardModel = cardCompare.getModel(dbStream);
932 						if (!MCard.hasIdCard(cardModel.getIdCard(), cardType)) {
933 							toRemove.add(cardCompare);
934 						}
935 					} catch (IOException e1) {
936 						e1.printStackTrace();
937 					}
938 				}
939 				model.removeAll(toRemove);
940 				toRemove.clear();
941 			}
942 
943 			// property filter
944 			// we search for the property value, if it isn't found it's because "All"
945 			// is selected in the comboBox
946 			int property = CardFactory.getProperty((String) propertiesComboBox
947 					.getSelectedItem());
948 
949 			if (property != -1) {
950 				// "All" is not selected in property filter
951 				// we remove the cards that don't have the selected property
952 				for (MCardCompare cardCompare : model.delegate) {
953 					try {
954 						final CardModel cardModel = cardCompare.getModel(dbStream);
955 						if (!MCard.hasProperty(cardModel.getProperties(), property)) {
956 							toRemove.add(cardCompare);
957 						}
958 					} catch (IOException e1) {
959 						e1.printStackTrace();
960 					}
961 				}
962 				model.removeAll(toRemove);
963 				toRemove.clear();
964 			}
965 
966 			// color filters
967 			for (int i = 1; i < IdCardColors.CARD_COLOR_VALUES.length + 1; i++) {
968 				final JToggleButton colorButton = (JToggleButton) toolBar
969 						.getComponent(i);
970 				if (!colorButton.isSelected()) {
971 					for (MCardCompare cardCompare : model.delegate) {
972 						try {
973 							final CardModel cardModel = cardCompare.getModel(dbStream);
974 							if (i == 1) {
975 								if (cardModel.getIdColor() == 0) {
976 									toRemove.add(cardCompare);
977 								}
978 							} else if ((cardModel.getIdColor() & IdCardColors.CARD_COLOR_VALUES[i - 1]) == IdCardColors.CARD_COLOR_VALUES[i - 1]) {
979 								toRemove.add(cardCompare);
980 							}
981 						} catch (IOException e1) {
982 							e1.printStackTrace();
983 						}
984 					}
985 					model.removeAll(toRemove);
986 					toRemove.clear();
987 				}
988 			}
989 			leftList.repaint();
990 			if (!model.isEmpty())
991 				leftList.setSelectedIndex(0);
992 		}
993 	}
994 
995 	/***
996 	 * Set the current TBS name.
997 	 * 
998 	 * @param tbsName
999 	 *          the TBS to define as current.
1000 	 */
1001 	protected void setToolKitMdb(String tbsName) {
1002 		MdbLoader.setToolKitMdb(tbsName);
1003 		readAvailableCards(allListModel);
1004 	}
1005 
1006 	public void mouseClicked(MouseEvent e) {
1007 		if (e.getSource() == leftList && e.getClickCount() == 2) {
1008 			refreshAddComponent(true);
1009 		} else if (e.getSource() == removeButton
1010 				&& rightList.getSelectedRow() != -1) {
1011 			removeCardFromDeck();
1012 		} else if (e.getSource() == cardNameTxt) {
1013 			cardNameTxt.selectAll();
1014 		}
1015 	}
1016 
1017 	@Override
1018 	public void windowClosing(WindowEvent e) {
1019 		exitForm();
1020 	}
1021 
1022 	/***
1023 	 * Set the working deck as new.
1024 	 */
1025 	protected void setAsNew() {
1026 		isNew = true;
1027 	}
1028 
1029 	/***
1030 	 * Set the working deck as saved.
1031 	 */
1032 	protected void setAsSaved() {
1033 		isNew = false;
1034 		modifiedSinceSave = false;
1035 		String filename = FilenameUtils.removeExtension(FilenameUtils
1036 				.getName(Configuration.getString("decks.deck(0)")));
1037 		setTitle("DeckBuilder : ".concat(filename));
1038 		deckNameTxt.setText(filename);
1039 	}
1040 
1041 	/***
1042 	 * Are we working with a new deck?
1043 	 */
1044 	private boolean isNew = false;
1045 
1046 	private final ChartSets datasets;
1047 
1048 	private Map<ChartFilter, Map<Integer, IChartKey>> keyMapper;
1049 
1050 	private Map<ChartFilter, Map<Integer, Paint>> painterMapper;
1051 
1052 	private void initSets() {
1053 		final Map<Integer, Paint> painterColorMapper = new TreeMap<Integer, Paint>();
1054 		for (int index = IdCardColors.CARD_COLOR_VALUES.length; index-- > 0;) {
1055 			painterColorMapper.put(IdCardColors.CARD_COLOR_VALUES[index],
1056 					IdCardColors.CARD_COLOR[index]);
1057 		}
1058 		painterColorMapper.put(ChartFilter.DEFAULT_KEY, Color.YELLOW.darker()
1059 				.darker());
1060 		painterMapper = new HashMap<ChartFilter, Map<Integer, Paint>>();
1061 		painterMapper.put(ChartFilter.color, painterColorMapper);
1062 
1063 		final Map<Integer, IChartKey> keyColorMapper = new TreeMap<Integer, IChartKey>();
1064 		for (int index = 0; index < IdCardColors.CARD_COLOR_VALUES.length; index++) {
1065 			keyColorMapper.put(IdCardColors.CARD_COLOR_VALUES[index], new CardColor(
1066 					LanguageManager.getString(IdCardColors.CARD_COLOR_NAMES[index]),
1067 					IdCardColors.CARD_COLOR_VALUES[index]));
1068 		}
1069 		keyColorMapper.put(ChartFilter.DEFAULT_KEY, CardColor.UNKNOW_COLOR);
1070 
1071 		final Map<Integer, IChartKey> keyTypeMapper = new TreeMap<Integer, IChartKey>();
1072 		for (int index = 0; index < CardFactory.exportedIdCardNames.length; index++) {
1073 			keyTypeMapper.put(CardFactory.exportedIdCardValues[index], new CardTypes(
1074 					CardFactory.exportedIdCardNames[index],
1075 					CardFactory.exportedIdCardValues[index]));
1076 		}
1077 		keyTypeMapper.put(ChartFilter.DEFAULT_KEY, CardTypes.UNKNOW_TYPE);
1078 
1079 		keyMapper = new HashMap<ChartFilter, Map<Integer, IChartKey>>();
1080 		keyMapper.put(ChartFilter.color, keyColorMapper);
1081 		keyMapper.put(ChartFilter.type, keyTypeMapper);
1082 		keyMapper.put(ChartFilter.property, new TreeMap<Integer, IChartKey>());
1083 		keyMapper.put(ChartFilter.manacost, new TreeMap<Integer, IChartKey>());
1084 	}
1085 
1086 	private Map<Integer, IChartKey> getMapper(ChartFilter filter) {
1087 		return keyMapper.get(filter);
1088 	}
1089 
1090 	public List<IChartKey> getKeys(ChartFilter filter) {
1091 		List<IChartKey> list = new ArrayList<IChartKey>(getMapper(filter).values());
1092 		Collections.sort(list);
1093 		return list;
1094 	}
1095 
1096 	public Collection<IChartKey> getKeys(CardModel cardModel, ChartFilter filter) {
1097 		Collection<IChartKey> result = new ArrayList<IChartKey>();
1098 		switch (filter) {
1099 		case color:
1100 			int idColor = cardModel.getIdColor();
1101 			if (idColor == 0)
1102 				result.add(getMapper(filter).get(IdCardColors.CARD_COLOR_VALUES[0]));
1103 			else {
1104 				for (IChartKey value : getMapper(filter).values()) {
1105 					if (value.getIntegerKey() != 0
1106 							&& MCard.hasIdColor(idColor, value.getIntegerKey())) {
1107 						result.add(value);
1108 					}
1109 				}
1110 			}
1111 			break;
1112 		case type:
1113 			int idCard = cardModel.getIdCard();
1114 			for (IChartKey value : getMapper(filter).values()) {
1115 				if (MCard.hasIdCard(idCard, value.getIntegerKey()))
1116 					result.add(value);
1117 			}
1118 			break;
1119 		case manacost:
1120 			int total = 0;
1121 			for (int index = IdCommonToken.WHITE_MANA + 1; index-- > 0;) {
1122 				total += cardModel.getStaticRegisters()[index];
1123 			}
1124 			result.add(new CardManaCost(total));
1125 			break;
1126 		default:
1127 			// TODO add the the other filters
1128 		}
1129 		if (result.isEmpty()) {
1130 			IChartKey defaultKey = getMapper(filter).get(ChartFilter.DEFAULT_KEY);
1131 			if (defaultKey != null)
1132 				result.add(getMapper(filter).get(ChartFilter.DEFAULT_KEY));
1133 		}
1134 		return result;
1135 	}
1136 
1137 	public void tableChanged(TableModelEvent e) {
1138 		if (e.getType() == TableModelEvent.UPDATE)
1139 			rightListModel.getCards().refresh();
1140 	}
1141 
1142 	/***
1143 	 * Read available cards from file and add them to the specified
1144 	 * <code>cardNames</code>
1145 	 * 
1146 	 * @param cardNames
1147 	 *          to this list method add card names
1148 	 */
1149 	protected void readAvailableCards(MListModel<MCardCompare> cardNames) {
1150 		final FileInputStream dbStream = MdbLoader.resetMdb();
1151 		listScrollerLeft.setVisible(false);
1152 		try {
1153 			cardNames.clear();
1154 			while (dbStream.available() > 2) {
1155 				// reads card name
1156 				final String cardName = MToolKit.readString(dbStream);
1157 
1158 				// reads card offset
1159 				final long offset = MToolKit.readInt24(dbStream);
1160 				final MCardCompare cardCompare = new MCardCompare(cardName, offset);
1161 				cardNames.add(cardCompare);
1162 			}
1163 		} catch (IOException ex2) {
1164 			// Ignore this error
1165 			ex2.printStackTrace();
1166 		} finally {
1167 			listScrollerLeft.setVisible(true);
1168 			cardNames.refresh();
1169 		}
1170 	}
1171 
1172 	private static final FileFilterPlus FILTER_DECK = new FileFilterPlus("dck",
1173 			"DCK : Shandalar deck file");
1174 
1175 	/***
1176 	 * The left list model containing all cards.
1177 	 */
1178 	protected final MListModel<MCardCompare> allListModel;
1179 
1180 	/***
1181 	 * The right table model containing the current deck cards.
1182 	 */
1183 	protected final MCardTableModel rightListModel;
1184 
1185 	private static boolean modifiedSinceSave = false;
1186 
1187 	private final JButton addButton;
1188 
1189 	private final JButton removeButton;
1190 
1191 	/***
1192 	 * The right list : cards in the deck.
1193 	 */
1194 	private final JTable rightList;
1195 
1196 	/***
1197 	 * The left list : available cards.
1198 	 */
1199 	protected final JList leftList;
1200 
1201 	private final JTextField cardNameTxt;
1202 
1203 	/***
1204 	 * Add text
1205 	 */
1206 	private final JTextField addQtyTxt;
1207 
1208 	/***
1209 	 * Remove text
1210 	 */
1211 	private final JTextField removeQtyTxt;
1212 
1213 	/***
1214 	 * The label displayed above the card picture
1215 	 */
1216 	private final JLabel cardPictureNameTxt;
1217 
1218 	/***
1219 	 * indicates if deck builder was launched from console
1220 	 */
1221 	public static boolean consoleMode = false;
1222 
1223 	/***
1224 	 * The toolBar containing some filters and card type combobox.
1225 	 */
1226 	private final JToolBar toolBar;
1227 
1228 	private final JComboBox idCardComboBox;
1229 
1230 	private final JComboBox propertiesComboBox;
1231 
1232 	private final JLabel oracleText;
1233 
1234 	private final ConstraintsChecker constraintsChecker;
1235 
1236 	/***
1237 	 * The deck title
1238 	 */
1239 	private final JTextField deckNameTxt;
1240 
1241 	/***
1242 	 * The unique instance of this form.
1243 	 */
1244 	public static DeckBuilder form;
1245 }