View Javadoc

1   /*
2    *   Magic-Project 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.magicproject.tools;
20  
21  import java.awt.Font;
22  import java.awt.Image;
23  import java.awt.MediaTracker;
24  import java.awt.Point;
25  import java.awt.Toolkit;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.net.HttpURLConnection;
32  import java.net.MalformedURLException;
33  import java.net.URL;
34  import java.net.URLConnection;
35  import java.nio.charset.Charset;
36  import java.util.Properties;
37  import java.util.Random;
38  
39  import javax.sound.sampled.AudioFormat;
40  import javax.sound.sampled.AudioInputStream;
41  import javax.sound.sampled.AudioSystem;
42  import javax.sound.sampled.Clip;
43  import javax.sound.sampled.DataLine;
44  import javax.sound.sampled.LineUnavailableException;
45  import javax.sound.sampled.SourceDataLine;
46  import javax.sound.sampled.UnsupportedAudioFileException;
47  import javax.swing.ImageIcon;
48  import javax.swing.JComponent;
49  import javax.swing.JFileChooser;
50  import javax.swing.JFrame;
51  import javax.swing.JOptionPane;
52  import javax.swing.LookAndFeel;
53  import javax.swing.SwingUtilities;
54  
55  import net.sf.magicproject.clickable.targetable.card.MCard;
56  import net.sf.magicproject.clickable.targetable.card.SystemCard;
57  import net.sf.magicproject.deckbuilder.MdbLoader;
58  import net.sf.magicproject.token.IdCardColors;
59  import net.sf.magicproject.token.IdCommonToken;
60  import net.sf.magicproject.token.IdConst;
61  import net.sf.magicproject.ui.MagicUIComponents;
62  import net.sf.magicproject.xml.XmlDeckTranslator;
63  
64  import org.apache.commons.io.IOUtils;
65  import org.apache.commons.lang.ArrayUtils;
66  import org.apache.commons.lang.StringUtils;
67  import org.jvnet.substance.SubstanceLookAndFeel;
68  import org.jvnet.substance.painter.AlphaControlBackgroundComposite;
69  import org.jvnet.substance.scroll.CoreScrollThumbGripPainters;
70  import org.mortbay.util.Password;
71  
72  import sun.misc.BASE64Encoder;
73  
74  /***
75   * MToolKit.java Created on 18 d�c. 2003
76   * 
77   * @author Fabrice Daugan, brius
78   * @since 0.53
79   * @since 0.54 added "save..." methods
80   */
81  public final class MToolKit {
82  
83  	/***
84  	 * Create a new instance of this class.
85  	 */
86  	private MToolKit() {
87  		super();
88  	}
89  
90  	/***
91  	 * Replace occurrences into a string.
92  	 * 
93  	 * @param data
94  	 *          the string to replace occurrences into
95  	 * @param from
96  	 *          the occurrence to replace.
97  	 * @param to
98  	 *          the occurrence to be used as a replacement.
99  	 * @return the new string with replaced occurrences.
100 	 */
101 	public static String replaceAll(String data, String from, String to) {
102 		if (from.length() > 0 && from.length() > 0) {
103 			final StringBuilder buf = new StringBuilder(data.length());
104 			int pos = -1;
105 			int i = 0;
106 			while ((pos = data.indexOf(from, i)) != -1) {
107 				buf.append(data.substring(i, pos)).append(to);
108 				i = pos + from.length();
109 			}
110 			buf.append(data.substring(i));
111 			return buf.toString();
112 		}
113 		return data;
114 	}
115 
116 	/***
117 	 * @param idToken
118 	 *          the value to translate.
119 	 * @return the real value of the specified value. If the value is a constant
120 	 *         the returned value can be positive or negative.
121 	 */
122 	public static int getConstant(int idToken) {
123 		if (idToken != IdConst.ALL
124 				&& (idToken & 0xFF80) == IdConst.NEGATIVE_NUMBER_MASK) {
125 			// a negative number
126 			return -(idToken & 0x7F);
127 		}
128 		return idToken;
129 	}
130 
131 	/***
132 	 * The default charset used by this application.
133 	 */
134 	public static final String CHARSET = Charset.forName("ISO-8859-1").name();
135 
136 	/***
137 	 * read a string from inputStream ending with \0. The mximum size of this
138 	 * string is 200 chars.
139 	 * 
140 	 * @param inputStream
141 	 *          is the input stream
142 	 * @return the read string
143 	 * @throws IOException
144 	 *           If some other I/O error occurs
145 	 */
146 	public static String readString(InputStream inputStream) throws IOException {
147 		int count = 0;
148 		try {
149 			while ((mBuffer[count] = (byte) inputStream.read()) != 0) {
150 				count++;
151 			}
152 		} catch (Exception e) {
153 			throw new IOException("Exception '" + e + "' reading string '"
154 					+ new String(mBuffer, 0, count, CHARSET) + "'\n\tdump : '"
155 					+ ArrayUtils.toString(mBuffer));
156 		}
157 
158 		// use : String(bytes[], int offset, int length)
159 		return new String(mBuffer, 0, count, CHARSET);
160 	}
161 
162 	/***
163 	 * write a string to output stream ending with \0.
164 	 * 
165 	 * @param out
166 	 *          is the input stream
167 	 * @param string
168 	 *          the string to write
169 	 */
170 	public static void writeString(OutputStream out, String string) {
171 		try {
172 			if (string != null) {
173 				out.write(string.getBytes(CHARSET));
174 			}
175 			out.write(0);
176 		} catch (Exception e) {
177 			throw new InternalError("writting string in file," + e);
178 		}
179 	}
180 
181 	/***
182 	 * read a string from inputFile The strings read end with \0 with no limit of
183 	 * size
184 	 * 
185 	 * @param inputStream
186 	 *          is the input stream containing content
187 	 * @return the read string from the specified input stream
188 	 */
189 	public static String readText(InputStream inputStream) {
190 		StringBuffer string = new StringBuffer(100);
191 		try {
192 			int intRead;
193 			while ((intRead = inputStream.read()) != 0) {
194 				string.append((char) intRead);
195 			}
196 		} catch (Exception e) {
197 			throw new InternalError("reading text in file," + e);
198 		}
199 		return string.toString();
200 	}
201 
202 	/***
203 	 * Read a byte array 256*256*256 bytes maximum sized from the given stream.
204 	 * 
205 	 * @param inputStream
206 	 *          is the input stream containing content.
207 	 * @return the byte array.
208 	 * @throws IOException
209 	 *           If some other I/O error occurs
210 	 */
211 	public static byte[] readByteArray(InputStream inputStream)
212 			throws IOException {
213 		final int size = MToolKit.readInt24(inputStream);
214 		final byte[] array = new byte[size];
215 		int readCounter = 0;
216 		while (readCounter != size) {
217 			readCounter += inputStream.read(array, readCounter, size - readCounter);
218 		}
219 		return array;
220 	}
221 
222 	/***
223 	 * Read an icon from the given stream.
224 	 * 
225 	 * @param inputStream
226 	 *          is the input stream containing content.
227 	 * @return the read icon.
228 	 * @throws IOException
229 	 *           If some other I/O error occurs
230 	 */
231 	public static ImageIcon readImageIcon(InputStream inputStream)
232 			throws IOException {
233 		return new ImageIcon(readByteArray(inputStream));
234 	}
235 
236 	/***
237 	 * Read an image from the given stream.
238 	 * 
239 	 * @param inputStream
240 	 *          is the input stream containing content.
241 	 * @return the read image.
242 	 * @throws IOException
243 	 *           If some other I/O error occurs
244 	 */
245 	public static Image readImage(InputStream inputStream) throws IOException {
246 		return Toolkit.getDefaultToolkit().createImage(readByteArray(inputStream));
247 	}
248 
249 	/***
250 	 * Return 0 or 1 at random way
251 	 * 
252 	 * @return 0 or 1 at random way
253 	 */
254 	public static int getRandom() {
255 		return getRandom(2);
256 	}
257 
258 	/***
259 	 * Return a number [0,length[ at random way
260 	 * 
261 	 * @param length
262 	 *          maximum+1 value to return.
263 	 * @return a number [0,length[ at random way
264 	 */
265 	public static int getRandom(int length) {
266 		lastRandom = random.nextInt(length);
267 		return lastRandom;
268 	}
269 
270 	/***
271 	 * Copy the src file throw the output stream.
272 	 * 
273 	 * @param src
274 	 *          the source file.
275 	 * @param out
276 	 *          the output stream.
277 	 * @throws IOException
278 	 */
279 	public static void writeFile(File src, OutputStream out) throws IOException {
280 		InputStream in = new FileInputStream(src);
281 		MToolKit.writeInt24(out, (int) src.length());
282 
283 		// Transfer bytes from in to out
284 		final byte[] buf = new byte[1024];
285 		int len;
286 		while ((len = in.read(buf)) >= 0) {
287 			out.write(buf, 0, len);
288 		}
289 		IOUtils.closeQuietly(in);
290 	}
291 
292 	/***
293 	 * file filter used for mdb files
294 	 */
295 	private static FileFilterPlus mdbFilter = new FileFilterPlus("mdb",
296 			"MDB File For Magic-Project");
297 
298 	/***
299 	 * file filter used for deck files
300 	 */
301 	private static FileFilterPlus txtFilter = new FileFilterPlus("txt",
302 			"TXT format: CardName;qty");
303 
304 	/***
305 	 * file filter used for JPG pictures
306 	 */
307 	private static FileFilterPlus pictureFilter = new FileFilterPlus(
308 			new String[] { "jpg", "gif", "png" }, "Images");
309 
310 	/***
311 	 * Return the deck file choosen with an "open" dialog.
312 	 * 
313 	 * @return the file choosen, or the previous one if cancelled.
314 	 */
315 	public static String getDeckFile() {
316 		return getDeckFile(JFileChooser.OPEN_DIALOG);
317 	}
318 
319 	/***
320 	 * Return the deck file choosen
321 	 * 
322 	 * @param type
323 	 *          open or save option
324 	 * @return the file choosen, or the previous one if cancelled.
325 	 */
326 	public static String getDeckFile(int type) {
327 		return getDeckFile(MagicUIComponents.magicForm, type);
328 	}
329 
330 	/***
331 	 * Return the deck file choosen
332 	 * 
333 	 * @param parent
334 	 *          is parent of this dialog
335 	 * @param type
336 	 *          open or save option
337 	 * @return the file choosen, or the previous one if cancelled.
338 	 */
339 	public static String getDeckFile(JFrame parent, int type) {
340 		String deckFile = Configuration.getString("decks.deck(0)", "");
341 		try {
342 			File file = MToolKit.getFile(deckFile);
343 			if (file == null) {
344 				file = showDialogFile("choose your deck file", null, null, 'o', file,
345 						txtFilter, parent, type);
346 			} else {
347 				file = showDialogFile("choose your deck file", null, null, 'o', file
348 						.getAbsoluteFile(), txtFilter, parent, type);
349 			}
350 
351 			if (file != null) {
352 				deckFile = getShortDeckFile(file.getCanonicalPath());
353 			} else
354 				return null;
355 			return deckFile;
356 
357 		} catch (IOException e) {
358 			JOptionPane.showMessageDialog(parent,
359 					"Error occurred reading the specified file", "File problem",
360 					JOptionPane.ERROR_MESSAGE);
361 			return Configuration.getString("decks.deck(0)", "");
362 		} catch (Exception e) {
363 			// cancel of user;
364 			return null;
365 		}
366 	}
367 
368 	/***
369 	 * Open a DialogFile dialog and return the choosen mdb file.
370 	 * 
371 	 * @param oldFile
372 	 *          the old mdb file, will be returned if user cancel operation
373 	 * @param parent
374 	 *          is parent of this dialog
375 	 * @return the file choosen, or oldFile if cancelled
376 	 */
377 	public static String getMdbFile(String oldFile, JFrame parent) {
378 		try {
379 			return showDialogFile("choose your mdb file", null, null, 'o',
380 					oldFile == null ? null : MToolKit.getFile(oldFile), mdbFilter,
381 					parent, JFileChooser.OPEN_DIALOG).getCanonicalPath();
382 		} catch (java.io.IOException e) {
383 			javax.swing.JOptionPane.showMessageDialog(parent,
384 					"Error occurred reading the specified file", "File problem",
385 					JOptionPane.ERROR_MESSAGE);
386 			return oldFile;
387 		} catch (Exception e) {
388 			// cancel of user;
389 			return null;
390 		}
391 	}
392 
393 	/***
394 	 * return the picture file choosen
395 	 * 
396 	 * @param oldFile
397 	 *          the old picture, will be returned if user cancel operation
398 	 * @param parent
399 	 *          is parent of this dialog
400 	 * @return the picture file choosen, or oldFile if cancelled
401 	 */
402 	public static String getPictureFile(String oldFile, JFrame parent) {
403 		try {
404 			return showDialogFile("choose your a picture", null, null, 'o', null,
405 					pictureFilter, parent, JFileChooser.OPEN_DIALOG).getCanonicalPath();
406 		} catch (java.io.IOException e) {
407 			throw new InternalError("choosing picture file");
408 		} catch (Exception e) {
409 			// cancel of user;
410 			return null;
411 		}
412 	}
413 
414 	/***
415 	 * Disp a file dialog with many options
416 	 * 
417 	 * @param titreBoite
418 	 *          title of this dialog box
419 	 * @param etiquetteBouton
420 	 *          button "yes"
421 	 * @param infoBulle
422 	 *          tooltip
423 	 * @param raccourciBouton
424 	 *          shortcut for "yes" button
425 	 * @param fichier
426 	 *          old file
427 	 * @param fileFilter
428 	 *          file filter to apply
429 	 * @param parent
430 	 *          is parent of this dialog
431 	 * @param type
432 	 *          the dialog type
433 	 * @return the File object correponding to the user's choice
434 	 * @see JFileChooser#setDialogType(int)
435 	 */
436 	private static File showDialogFile(String titreBoite, String etiquetteBouton,
437 			String infoBulle, char raccourciBouton, File fichier,
438 			FileFilterPlus fileFilter, JFrame parent, int type) {
439 		return showDialogFile(titreBoite, etiquetteBouton, infoBulle,
440 				raccourciBouton, fichier, fileFilter, parent, type,
441 				JFileChooser.FILES_AND_DIRECTORIES);
442 	}
443 
444 	/***
445 	 * Disp a file dialog with many options
446 	 * 
447 	 * @param titreBoite
448 	 *          title of this dialog box
449 	 * @param etiquetteBouton
450 	 *          button "yes"
451 	 * @param infoBulle
452 	 *          tooltip
453 	 * @param raccourciBouton
454 	 *          shortcut for "yes" button
455 	 * @param fichier
456 	 *          old file
457 	 * @param fileFilter
458 	 *          file filter to apply
459 	 * @param parent
460 	 *          is parent of this dialog
461 	 * @param type
462 	 *          the dialog type
463 	 * @param mode
464 	 * @return the File object correponding to the user's choice
465 	 * @see JFileChooser#setDialogType(int)
466 	 */
467 	public static File showDialogFile(String titreBoite, String etiquetteBouton,
468 			String infoBulle, char raccourciBouton, File fichier,
469 			FileFilterPlus fileFilter, JFrame parent, int type, int mode) {
470 		if (fileChooser == null) {
471 			fileChooser = new JFileChooser();
472 		}
473 		fileChooser.setDialogType(type);
474 		fileChooser.setDialogTitle(titreBoite);
475 		fileChooser.setApproveButtonText(etiquetteBouton);
476 		fileChooser.setApproveButtonToolTipText(infoBulle);
477 		fileChooser.setApproveButtonMnemonic(raccourciBouton);
478 		if (fileFilter != null) {
479 			fileFilter.addExtension(".");
480 			fileChooser.setFileFilter(fileFilter);
481 		}
482 		fileChooser.setFileSelectionMode(mode);
483 		fileChooser.rescanCurrentDirectory();
484 		fileChooser.setSelectedFile(fichier);
485 		/*
486 		 * if (filePreview != null) { filePreview.setFileChooser(fileChooser);
487 		 * fileChooser.setAccessory(filePreview); }
488 		 */
489 
490 		int resultat = fileChooser.showDialog(parent, null);
491 
492 		return resultat == JFileChooser.APPROVE_OPTION ? fileChooser
493 				.getSelectedFile() : null;
494 	}
495 
496 	/***
497 	 * An utility function that layers on top of the LookAndFeel's
498 	 * isSupportedLookAndFeel() method. Returns true if the LookAndFeel is
499 	 * supported. Returns false if the LookAndFeel is not supported and/or if
500 	 * there is any kind of error checking if the LookAndFeel is supported. The
501 	 * L&F menu will use this method to detemine whether the various L&F options
502 	 * should be active or inactive.
503 	 * 
504 	 * @param laf
505 	 *          L1F name
506 	 * @return true if successfully loaded
507 	 */
508 	public static boolean isAvailableLookAndFeel(String laf) {
509 		try {
510 			Class.forName(laf);
511 			// final Class lnfClass = Class.forName(laf);
512 			// final LookAndFeel newLAF = (LookAndFeel) (lnfClass.newInstance());
513 			// return newLAF.isSupportedLookAndFeel();
514 			return true;
515 		} catch (Exception e) { // If ANYTHING weird happens, return false
516 			// try {
517 			// final Class lnfClass2 = JarClassLoader.getInstance().loadClass(laf);
518 			// final LookAndFeel newLAF = (LookAndFeel) (lnfClass2.newInstance());
519 			// return newLAF.isSupportedLookAndFeel();
520 			// } catch (Exception e2) { // If ANYTHING weird happens, return false
521 			return false;
522 			// }
523 		}
524 	}
525 
526 	/***
527 	 * Return the L&F instance from the givent name.
528 	 * 
529 	 * @param laf
530 	 *          the L&F class name
531 	 * @return the L&F instance
532 	 * @throws InstantiationException
533 	 * @throws IllegalAccessException
534 	 * @throws ClassNotFoundException
535 	 */
536 	public static LookAndFeel geLookAndFeel(String laf)
537 			throws InstantiationException, IllegalAccessException,
538 			ClassNotFoundException {
539 		final Class<?> lnfClass = Class.forName(laf);
540 		return (LookAndFeel) (lnfClass.newInstance());
541 	}
542 
543 	/***
544 	 * Read the next 2 bytes from the specified input stream and return the
545 	 * integer value coded with 32bits
546 	 * 
547 	 * @param inputFile
548 	 *          is the file containing the short (2bytes)to read
549 	 * @return the read integer built with the 2 next bytes read from the
550 	 *         specified input stream
551 	 * @throws IOException
552 	 *           if error occurred during the reading process from the specified
553 	 *           input stream
554 	 */
555 	public static int readInt16(InputStream inputFile) throws IOException {
556 		int res = inputFile.read() * 256;
557 		return res + inputFile.read();
558 	}
559 
560 	/***
561 	 * Read the next 3 bytes from the specified input stream and return the
562 	 * integer value coded with 48bits
563 	 * 
564 	 * @param inputFile
565 	 *          is the file containing the short (3bytes)to read
566 	 * @return the read integer built with the 3 next bytes read from the
567 	 *         specified input stream
568 	 * @throws IOException
569 	 *           if error occurred during the reading process from the specified
570 	 *           input stream
571 	 */
572 	public static int readInt24(InputStream inputFile) throws IOException {
573 		int res = inputFile.read() * 256 * 256;
574 		res += inputFile.read() * 256;
575 		return res + inputFile.read();
576 	}
577 
578 	/***
579 	 * Write the specified positive short coded on 2 bytes to the specified input
580 	 * stream
581 	 * 
582 	 * @param out
583 	 *          is the output stream where the specified integer would be written
584 	 * @param int16
585 	 *          the 2 bytes integer to write
586 	 */
587 	public static void writeInt16(OutputStream out, int int16) {
588 		try {
589 			out.write(int16 / 256);
590 			out.write(int16 % 256);
591 		} catch (Exception e) {
592 			throw new InternalError("writting int16 in file," + e);
593 		}
594 	}
595 
596 	/***
597 	 * Write the specified positive int coded on 3 bytes to the specified input
598 	 * stream
599 	 * 
600 	 * @param out
601 	 *          is the output stream where the specified integer would be written
602 	 * @param int24
603 	 *          the 3 bytes integer to write
604 	 */
605 	public static void writeInt24(OutputStream out, int int24) {
606 		try {
607 			out.write(int24 / 65536);
608 			out.write(int24 % 65536 / 256);
609 			out.write(int24 % 256);
610 		} catch (Exception e) {
611 			throw new InternalError("writting int24 in file," + e);
612 		}
613 	}
614 
615 	/***
616 	 * Create, and return the connection etablished with a http server. May use a
617 	 * http proxy if the settings have been set
618 	 * 
619 	 * @return the SMTP properties.
620 	 */
621 	public static Properties getSmtpProperties() {
622 		if (Configuration.getBoolean("useProxy", false)) {
623 			// we use the proxy configuration
624 			System.setProperty("socksProxySet", "true");
625 			System.setProperty("socksProxyHost", Configuration.getString("proxyHost",
626 					"192.168.0.252"));
627 			System.setProperty("socksProxyPort", "25");
628 			final String clearLoginPwd = Password.deobfuscate(Configuration
629 					.getString("proxyObfuscatedLoginPwd", "anonymous:"));
630 			System.setProperty("socksProxyUserName", clearLoginPwd.substring(0,
631 					clearLoginPwd.indexOf(':')));
632 			System.setProperty("socksProxyPassword", clearLoginPwd
633 					.substring(clearLoginPwd.indexOf(':') + 1));
634 
635 			System.setProperty("socks.useProxy", "true");
636 			System.setProperty("socks.proxyHost", System
637 					.getProperty("socksProxyHost"));
638 			System.setProperty("socks.proxyPort", System
639 					.getProperty("socksProxyPort"));
640 			System.setProperty("socks.proxyUserName", System
641 					.getProperty("socksProxyUserName"));
642 			System.setProperty("socks.proxyPassword", System
643 					.getProperty("socksProxyPassword"));
644 		}
645 		return System.getProperties();
646 	}
647 
648 	/***
649 	 * Create, and return the connection etablished with a http server. May use a
650 	 * http proxy if the settings have been set
651 	 * 
652 	 * @param url
653 	 *          the requested url.
654 	 * @return Http connection.
655 	 */
656 	public static URLConnection getHttpConnection(URL url) {
657 		try {
658 			if (Configuration.getBoolean("useProxy", false)) {
659 				// we use the proxy configuration
660 				Properties systPrp = System.getProperties();
661 				systPrp.put("proxySet", "true");
662 				systPrp.put("http.proxyHost", Configuration.getString("proxyHost",
663 						"192.168.0.252"));
664 				systPrp.put("http.proxyPort", Configuration.getInt("proxyPort", 1299));
665 				System.setProperties(systPrp);
666 
667 				HttpURLConnection uc = (HttpURLConnection) url.openConnection();
668 				BASE64Encoder encoder = new sun.misc.BASE64Encoder();
669 				String encodedUserPwd = encoder.encode(Password.deobfuscate(
670 						Configuration.getString("proxyObfuscatedLoginPwd", "")).getBytes());
671 				uc.setRequestProperty("Proxy-Authorization", "Basic " + encodedUserPwd);
672 				uc.connect();
673 				return uc;
674 			}
675 			return url.openConnection();
676 		} catch (IOException e) {
677 			return null;
678 		}
679 	}
680 
681 	/***
682 	 * Return the loaded picture from a local place.
683 	 * 
684 	 * @param localFile
685 	 *          the local file name.
686 	 * @return the local picture.
687 	 * @throws InterruptedException
688 	 */
689 	public static Image getLocalPicture(String localFile)
690 			throws InterruptedException {
691 		final Image result = Toolkit.getDefaultToolkit().getImage(
692 				getResource(localFile, true));
693 		if (result == null) {
694 			throw new InterruptedException("Picture " + localFile
695 					+ " has not been found");
696 		}
697 		final MediaTracker tracker = new MediaTracker(MagicUIComponents.magicForm);
698 		tracker.addImage(result, 0);
699 		tracker.waitForAll();
700 		if (tracker.isErrorAny()) {
701 			tracker.removeImage(result, 0);
702 			tracker.waitForAll();
703 			result.flush();
704 			throw new InterruptedException("Malformed picture " + localFile);
705 		}
706 		return result;
707 	}
708 
709 	/***
710 	 * Return the picture specific to the current TBS
711 	 * 
712 	 * @param pictureFile
713 	 *          the path of picture to load. This file must not begin with '/'
714 	 * @return the picture specific to the current TBS
715 	 */
716 	public static String getTbsHtmlPicture(String pictureFile) {
717 		return new File(new StringBuilder(IdConst.TBS_DIR).append("/").append(
718 				tbsName).append("/").append(IdConst.IMAGES_DIR).append(pictureFile)
719 				.toString()).getAbsolutePath().replace('//', '/');
720 	}
721 
722 	/***
723 	 * Return the picture specific to the current TBS
724 	 * 
725 	 * @param pictureFile
726 	 *          the path of picture to load. This file must not begin with '/'
727 	 * @return the picture specific to the current TBS
728 	 */
729 	public static String getTbsPicture(String pictureFile) {
730 		return getTbsPicture(pictureFile, true);
731 	}
732 
733 	/***
734 	 * Return the picture specific to the current TBS
735 	 * 
736 	 * @param pictureFile
737 	 *          the path of picture to load. This file must not begin with '/'
738 	 * @param mustExist
739 	 *          <code>true</code> if the ressource to search must exists,
740 	 *          <code>false</code> either
741 	 * @return the picture specific to the current TBS
742 	 */
743 	public static String getTbsPicture(String pictureFile, boolean mustExist) {
744 		return getTbsFile(IdConst.IMAGES_DIR + pictureFile, mustExist);
745 	}
746 
747 	/***
748 	 * Return the mana picture specific to the current TBS
749 	 * 
750 	 * @param idColor
751 	 *          the color of mana
752 	 * @return the mana picture specific to the current TBS
753 	 */
754 	public static ImageIcon getTbsBigManaPicture(int idColor) {
755 		if (idColor == 0)
756 			return new ImageIcon(getTbsPicture("mana/colorless/big/"
757 					+ MdbLoader.colorlessBigURL));
758 		return new ImageIcon(getTbsPicture("mana/colored/big/"
759 				+ MdbLoader.coloredBigManas[idColor]));
760 	}
761 
762 	/***
763 	 * Return the mana picture specific to the current TBS
764 	 * 
765 	 * @param idColor
766 	 *          the color of mana
767 	 * @param amount
768 	 *          requested amount.
769 	 * @return the mana picture specific to the current TBS
770 	 */
771 	public static String getHtmlMana(int idColor, int amount) {
772 		if (idColor == IdCommonToken.COLORLESS_MANA) {
773 			if (amount >= MdbLoader.colorlessSmlManas.length) {
774 				return MdbLoader.colorlessSmlManasHtml[MdbLoader.colorlessSmlManas.length - 1]
775 						+ getHtmlMana(idColor, amount - MdbLoader.colorlessSmlManas.length
776 								+ 1);
777 			} else if (amount == -1) {
778 				return MdbLoader.unknownSmlManaHtml;
779 			}
780 			return MdbLoader.colorlessSmlManasHtml[amount];
781 
782 		}
783 		if (amount == -1) {
784 			return MdbLoader.unknownSmlManaHtml;
785 		}
786 
787 		String res = "";
788 		for (int i = amount; i-- > 0;) {
789 			res += MdbLoader.coloredSmlManasHtml[idColor];
790 		}
791 		return res;
792 	}
793 
794 	/***
795 	 * Return the file specific to the current TBS
796 	 * 
797 	 * @param file
798 	 *          the path of file to load. This file must not begin with '/'
799 	 * @return the picture specific to the current TBS
800 	 */
801 	public static String getTbsFile(String file) {
802 		return getTbsFile(file, true);
803 	}
804 
805 	/***
806 	 * Return the file specific to the current TBS
807 	 * 
808 	 * @param file
809 	 *          the path of file to load. This file must not begin with '/'
810 	 * @param mustExist
811 	 *          <code>true</code> if the ressource to search must exists,
812 	 *          <code>false</code> either
813 	 * @return the file specific to the current TBS
814 	 */
815 	public static String getTbsFile(String file, boolean mustExist) {
816 		File simpleFile = new File(new StringBuilder(IdConst.TBS_DIR).append("/")
817 				.append(tbsName).append("/").append(file).toString());
818 		if (simpleFile.exists()) {
819 			return simpleFile.getPath();
820 		}
821 		URL url = getResource(new StringBuilder(IdConst.TBS_DIR).append("/")
822 				.append(tbsName + "/").append(file).toString(), mustExist);
823 		if (url == null)
824 			return null;
825 		String filePath = url.getFile();
826 		File f = new File(filePath);
827 		if (f.exists()) {
828 			return filePath;
829 		} else if (filePath.startsWith("/")) {
830 			filePath = filePath.substring(1);
831 			f = new File(filePath);
832 			if (f.exists())
833 				return filePath;
834 		}
835 		if (mustExist)
836 			return null;
837 
838 		return filePath;
839 	}
840 
841 	/***
842 	 * Return the sound specific to the current TBS
843 	 * 
844 	 * @param soundFile
845 	 *          the path of picture to load. This file must not begin with '/'
846 	 * @return the sound specific to the current TBS
847 	 */
848 	public static String getSoundFile(String soundFile) {
849 		return new StringBuilder(IdConst.TBS_DIR).append("/").append(tbsName + "/")
850 				.append(IdConst.SOUNDS_DIR).append(soundFile).toString();
851 	}
852 
853 	/***
854 	 * loadClip loads the sound-file into a clip.
855 	 * 
856 	 * @param soundFile
857 	 *          file to be loaded and played.
858 	 */
859 	public static void loadClip(String soundFile) {
860 		AudioFormat audioFormat = null;
861 		AudioInputStream actionIS = null;
862 		try {
863 			// actionIS = AudioSystem.getAudioInputStream(input); // Does not work !
864 			actionIS = AudioSystem.getAudioInputStream(MToolKit.getFile(MToolKit
865 					.getSoundFile(soundFile)));
866 			AudioFormat.Encoding targetEncoding = AudioFormat.Encoding.PCM_SIGNED;
867 			actionIS = AudioSystem.getAudioInputStream(targetEncoding, actionIS);
868 			audioFormat = actionIS.getFormat();
869 
870 		} catch (UnsupportedAudioFileException afex) {
871 			Log.error(afex);
872 		} catch (IOException ioe) {
873 
874 			if (ioe.getMessage().equalsIgnoreCase("mark/reset not supported")) { // Ignore
875 				Log.error("IOException ignored.");
876 			}
877 			Log.error(ioe.getStackTrace());
878 		}
879 
880 		// define the required attributes for our line,
881 		// and make sure a compatible line is supported.
882 
883 		// get the source data line for playback.
884 		DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
885 		if (!AudioSystem.isLineSupported(info)) {
886 			Log.error("LineCtrl matching " + info + " not supported.");
887 			return;
888 		}
889 
890 		// Open the source data line for playback.
891 		try {
892 			Clip clip = null;
893 			try {
894 				Clip.Info info2 = new Clip.Info(Clip.class, audioFormat);
895 				clip = (Clip) AudioSystem.getLine(info2);
896 				clip.open(actionIS);
897 				clip.start();
898 			} catch (IOException ioe) {
899 				Log.error(ioe);
900 			}
901 		} catch (LineUnavailableException ex) {
902 			Log.error("Unable to open the line: " + ex);
903 			return;
904 		}
905 	}
906 
907 	/***
908 	 * Represents the default font
909 	 */
910 	public static Font defaultFont;
911 
912 	/***
913 	 * Represents the MDB file corresponding to the current TBS name. Is null if
914 	 * no TBS is currently defined.
915 	 */
916 	public static String mdbFile;
917 
918 	/***
919 	 * Represents the default mdb name. This is not the full name of selected TBS.
920 	 */
921 	public static String tbsName;
922 
923 	/***
924 	 * represents the file chooser
925 	 */
926 	public static JFileChooser fileChooser;
927 
928 	private static byte[] mBuffer = new byte[200];
929 
930 	static int lastRandom = 0; // Last integer generated
931 
932 	/***
933 	 * The current random sequence. May be initialized at the beginning of each
934 	 * play.
935 	 */
936 	public static Random random = new Random(); // Random generator
937 
938 	/***
939 	 * Return absolute location of given component.
940 	 * 
941 	 * @param component
942 	 *          the component to locate
943 	 * @return the absolute POINT location of given component.
944 	 */
945 	public static Point getAbsoluteLocation(JComponent component) {
946 		return SwingUtilities.convertPoint(component, 0, 0,
947 				MagicUIComponents.magicForm.getContentPane());
948 	}
949 
950 	/***
951 	 * Parses the string argument as a signed integer in the radix specified by
952 	 * the second argument. The characters in the string must all be digits of 10
953 	 * radix (as determined by whether
954 	 * {@link java.lang.Character#digit(char, int)} returns a nonnegative value),
955 	 * except that the first character may be an ASCII minus sign <code>'-'</code> (<code>'&#92;u002D'</code>)
956 	 * to indicate a negative value. The resulting integer value is returned.
957 	 * <p>
958 	 * The <code>Integer#MIN_VALUE</code> value is returned if any of the
959 	 * following situations occurs:
960 	 * <ul>
961 	 * <li>The first argument is <code>null</code> or is a string of length
962 	 * zero.
963 	 * <li>Any character of the string is not a digit of the specified radix,
964 	 * except that the first character may be a minus sign <code>'-'</code> (<code>'&#92;u002D'</code>)
965 	 * provided that the string is longer than length 1.
966 	 * <li>The value represented by the string is not a value of type
967 	 * <code>int</code>.
968 	 * </ul>
969 	 * <p>
970 	 * Examples: <blockquote> parseInt(&quot;0&quot;) returns 0 <br>
971 	 * parseInt(&quot;473&quot;) returns 473 <br>
972 	 * parseInt(&quot;-0&quot;) returns 0 <br>
973 	 * parseInt(&quot;2147483647&quot;) returns 2147483647 <br>
974 	 * parseInt(&quot;-2147483648&quot;) returns -2147483648 <br>
975 	 * parseInt(&quot;2147483648&quot;) returns Integer#MIN_VALUE <br>
976 	 * parseInt(&quot;Kona&quot;) returns Integer#MIN_VALUE <br>
977 	 * </blockquote>
978 	 * 
979 	 * @param s
980 	 *          the <code>String</code> containing the integer representation to
981 	 *          be parsed
982 	 * @return the integer represented by the string argument in the 10 radix.
983 	 *         Return Integer.MIN_VALUE when error
984 	 * @see Integer#MIN_VALUE
985 	 */
986 	public static int parseInt(String s) {
987 		if (s == null) {
988 			return Integer.MIN_VALUE;
989 		}
990 		int result = 0;
991 		boolean negative = false;
992 		int i = 0;
993 		int max = s.length();
994 		int limit;
995 		int multmin;
996 		int digit;
997 
998 		if (max > 0) {
999 			if (s.charAt(0) == '-') {
1000 				negative = true;
1001 				limit = Integer.MIN_VALUE;
1002 				i++;
1003 			} else {
1004 				limit = -Integer.MAX_VALUE;
1005 			}
1006 			multmin = limit / 10;
1007 			if (i < max) {
1008 				digit = Character.digit(s.charAt(i++), 10);
1009 				if (digit < 0) {
1010 					return Integer.MIN_VALUE;
1011 				}
1012 				result = -digit;
1013 			}
1014 			while (i < max) {
1015 				// Accumulating negatively avoids surprises near MAX_VALUE
1016 				digit = Character.digit(s.charAt(i++), 10);
1017 				if (digit < 0) {
1018 					return Integer.MIN_VALUE;
1019 				}
1020 				if (result < multmin) {
1021 					return Integer.MIN_VALUE;
1022 				}
1023 				result *= 10;
1024 				if (result < limit + digit) {
1025 					return Integer.MIN_VALUE;
1026 				}
1027 				result -= digit;
1028 			}
1029 		} else {
1030 			return Integer.MIN_VALUE;
1031 		}
1032 		if (negative) {
1033 			if (i > 1) {
1034 				return result;
1035 			}
1036 			/* Only got "-" */
1037 			return Integer.MIN_VALUE;
1038 		}
1039 		return -result;
1040 	}
1041 
1042 	/***
1043 	 * Return the canonical path of working directory.
1044 	 * 
1045 	 * @return the canonical path of working directory.
1046 	 * @throws IOException
1047 	 */
1048 	public static String getRelativePath() throws IOException {
1049 		return new File("").getAbsoluteFile().getCanonicalPath().replace('//', '/');
1050 	}
1051 
1052 	/***
1053 	 * Return the specified card name without any special char. The returned name
1054 	 * may be used to build a file. The returned string is in lower case.
1055 	 * 
1056 	 * @param cardName
1057 	 *          the card name as it is displayed.
1058 	 * @return the specified card name without any special char.
1059 	 */
1060 	public static String getKeyName(String cardName) {
1061 		return getExactKeyName(cardName).toLowerCase();
1062 	}
1063 
1064 	/***
1065 	 * Return the specified card name without any special char. The returned name
1066 	 * may be used to build a file.
1067 	 * 
1068 	 * @param cardName
1069 	 *          the card name as it is displayed.
1070 	 * @return the specified card name without any special char.
1071 	 */
1072 	public static String getExactKeyName(String cardName) {
1073 		if (translator == null) {
1074 			try {
1075 				translator = new XmlDeckTranslator(MToolKit.tbsName);
1076 			} catch (Exception e1) {
1077 				e1.printStackTrace();
1078 				Log.warn("NO DECK TRANSLATOR FOUND");
1079 			}
1080 		}
1081 		return translator.convert(cardName);
1082 	}
1083 
1084 	/***
1085 	 * Returns the given path with '\' char replaced by '/' char.
1086 	 * 
1087 	 * @param canonicalPath
1088 	 * @return the given path with '\' char replaced by '/' char.
1089 	 * @throws IOException
1090 	 */
1091 	public static String getRelativePath(String canonicalPath) throws IOException {
1092 		if (canonicalPath.replace('//', '/').startsWith(getRelativePath())) {
1093 			return canonicalPath.substring(getRelativePath().length() + 1).replace(
1094 					'//', '/');
1095 		}
1096 		return canonicalPath;
1097 	}
1098 
1099 	/***
1100 	 * Return logging info of the given card.
1101 	 * 
1102 	 * @param card
1103 	 *          the card to Log.
1104 	 * @return logging info of the given card.
1105 	 */
1106 	public static String getLogCardInfo(MCard card) {
1107 		if (card != null) {
1108 			if (card != SystemCard.instance) {
1109 				return new StringBuilder(", card=").append(card.getName()).append(
1110 						"@" + Integer.toHexString(card.hashCode())).toString();
1111 			}
1112 			return ", card=" + card.getName();
1113 		}
1114 		return "";
1115 	}
1116 
1117 	/***
1118 	 * The translator tranforming a card name into a file-serializable value. Is
1119 	 * <code>null</code> while not used.
1120 	 */
1121 	public static XmlDeckTranslator translator = null;
1122 
1123 	/***
1124 	 * Return the specfied string without any local white spaces. Would be
1125 	 * replaced when
1126 	 * {@link org.apache.commons.lang.StringUtils#deleteWhitespace(String)} would
1127 	 * work.
1128 	 * 
1129 	 * @param string
1130 	 *          the string to normalize.
1131 	 * @return the given string without any space char.
1132 	 * @see Character#isWhitespace(char)
1133 	 */
1134 	public static String replaceWhiteSpaces(String string) {
1135 		final StringBuilder workingString = new StringBuilder(StringUtils
1136 				.deleteWhitespace(string));
1137 		for (int count = workingString.length(); count-- > 0;) {
1138 			if (Character.isSpaceChar(workingString.charAt(count))) {
1139 				// delete this char
1140 				workingString.deleteCharAt(count);
1141 			}
1142 		}
1143 		return workingString.toString();
1144 	}
1145 
1146 	/***
1147 	 * Return the stream associated to the given resource.
1148 	 * 
1149 	 * @param resource
1150 	 *          the resource path to search.
1151 	 * @return the stream associated to the given resource.
1152 	 */
1153 	public static InputStream getResourceAsStream(String resource) {
1154 		if (new File(resource).exists())
1155 			try {
1156 				return new FileInputStream(resource);
1157 			} catch (Exception e) {
1158 				// Error, so ignore it and get data from another way
1159 			}
1160 		return Thread.currentThread().getContextClassLoader().getResourceAsStream(
1161 				resource);
1162 	}
1163 
1164 	/***
1165 	 * Return the URL associated to the given resource.
1166 	 * 
1167 	 * @param resource
1168 	 *          the resource path to search.
1169 	 * @param mustExist
1170 	 *          <code>true</code> if the ressource to search must exists,
1171 	 *          <code>false</code> either
1172 	 * @return the URL associated to the given resource.
1173 	 */
1174 	public static URL getResource(String resource, boolean mustExist) {
1175 		if (new File(resource).exists()) {
1176 			try {
1177 				return new File(resource).toURI().toURL();
1178 			} catch (Exception e) {
1179 				// Error, so ignore it and get data from another way
1180 			}
1181 		}
1182 		URL url = null;
1183 		try {
1184 			url = new URL(resource);
1185 		} catch (MalformedURLException e0) {
1186 			//
1187 		}
1188 		if (url != null)
1189 			return url;
1190 		if (!mustExist) {
1191 			try {
1192 				return new URL("file:///" + resource);
1193 			} catch (MalformedURLException e) {
1194 				e.printStackTrace();
1195 			}
1196 		}
1197 		return null;
1198 	}
1199 
1200 	/***
1201 	 * Return the File associated to the given fileName.
1202 	 * 
1203 	 * @param fileName
1204 	 *          the file name path to search.
1205 	 * @return the File associated to the given fileName.
1206 	 */
1207 	public static File getFile(String fileName) {
1208 		File simpleFile = new File(fileName);
1209 		if (simpleFile.exists())
1210 			return simpleFile;
1211 		URL url = getResource(fileName, true);
1212 		if (url == null) {
1213 			return null;
1214 		}
1215 		String file = url.getFile();
1216 		File f = new File(file);
1217 		if (f.exists()) {
1218 			return f;
1219 		} else if (file.startsWith("/")) {
1220 			file = file.substring(1);
1221 			f = new File(file);
1222 			if (f.exists())
1223 				return f;
1224 		}
1225 		return null;
1226 	}
1227 
1228 	/***
1229 	 * Returns the absolute path of an icon used for the UI.
1230 	 * 
1231 	 * @param fileName
1232 	 *          the icon path relative to the images dir
1233 	 * @return the absolute path of an icon used for the UI
1234 	 */
1235 	public static String getIconPath(String fileName) {
1236 		File file = getFile(IdConst.IMAGES_DIR + fileName);
1237 		if (file == null)
1238 			return "";
1239 		return file.getAbsolutePath();
1240 	}
1241 
1242 	/***
1243 	 * Return the given deck file without the current path off application. If the
1244 	 * given deck is valid, it becomes the new deck reference.
1245 	 * 
1246 	 * @param deckFile
1247 	 *          the full deck file.
1248 	 * @return the given deck file without the current path off application.
1249 	 */
1250 	public static String getShortDeckFile(String deckFile) {
1251 		String fullDeckFile = deckFile.replace('//', '/');
1252 		String shortName = fullDeckFile;
1253 		try {
1254 			if (fullDeckFile.toUpperCase().startsWith(
1255 					MToolKit.getRelativePath().toUpperCase())) {
1256 				shortName = fullDeckFile.substring(
1257 						MToolKit.getRelativePath().length() + 1).replace('//', '/');
1258 			}
1259 		} catch (IOException e) {
1260 			// we'll use the full deck name
1261 		}
1262 		Configuration.addRecentProperty("decks.deck", shortName);
1263 		return shortName;
1264 	}
1265 
1266 	/***
1267 	 * Specifies that a component should have overlay functionality.
1268 	 * 
1269 	 * @param superPanel
1270 	 *          the panel to overlay.
1271 	 */
1272 	public static void addOverlay(JComponent superPanel) {
1273 		/*
1274 		 * superPanel.putClientProperty(SubstanceLookAndFeel.OVERLAY_PROPERTY,
1275 		 * Boolean.TRUE);
1276 		 */
1277 		superPanel.putClientProperty(SubstanceLookAndFeel.BACKGROUND_COMPOSITE,
1278 				new AlphaControlBackgroundComposite(0.3f, 0.5f));
1279 		superPanel.putClientProperty(SubstanceLookAndFeel.SCROLLBAR_GRIP_PAINTER,
1280 				new CoreScrollThumbGripPainters.DragBumpsScrollThumbGripPainter());
1281 	}
1282 
1283 	/***
1284 	 * Sum the mana.
1285 	 * 
1286 	 * @param registers
1287 	 *          teh array of mana.
1288 	 * @return the sum of mana.
1289 	 */
1290 	public static int manaPool(int[] registers) {
1291 		int res = 0;
1292 		for (int i = IdCardColors.CARD_COLOR_NAMES.length; i-- > 0;) {
1293 			res += registers[i];
1294 		}
1295 		return res;
1296 	}
1297 
1298 }