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.clickable.target.card;
20  
21  import static net.sf.firemox.clickable.target.card.CardFactory.ROTATE_SCALE;
22  
23  import java.awt.AlphaComposite;
24  import java.awt.Color;
25  import java.awt.Container;
26  import java.awt.Cursor;
27  import java.awt.Dimension;
28  import java.awt.Font;
29  import java.awt.Graphics;
30  import java.awt.Graphics2D;
31  import java.awt.Point;
32  import java.awt.RenderingHints;
33  import java.awt.event.MouseEvent;
34  import java.awt.event.MouseListener;
35  import java.awt.event.MouseMotionListener;
36  import java.awt.geom.Rectangle2D;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Random;
40  
41  import javax.swing.JComponent;
42  
43  import net.sf.firemox.action.WaitActivatedChoice;
44  import net.sf.firemox.action.target.ChosenTarget;
45  import net.sf.firemox.clickable.ability.Ability;
46  import net.sf.firemox.clickable.target.TargetFactory;
47  import net.sf.firemox.database.DatabaseCard;
48  import net.sf.firemox.database.DatabaseFactory;
49  import net.sf.firemox.stack.StackManager;
50  import net.sf.firemox.stack.TargetHelper;
51  import net.sf.firemox.token.IdCardColors;
52  import net.sf.firemox.token.IdCommonToken;
53  import net.sf.firemox.token.IdTokens;
54  import net.sf.firemox.token.IdZones;
55  import net.sf.firemox.tools.Configuration;
56  import net.sf.firemox.tools.Log;
57  import net.sf.firemox.tools.StatePicture;
58  import net.sf.firemox.ui.MagicUIComponents;
59  import net.sf.firemox.ui.Reversable;
60  import net.sf.firemox.ui.Tappable;
61  import net.sf.firemox.ui.TooltipFilter;
62  import net.sf.firemox.ui.i18n.LanguageManager;
63  import net.sf.firemox.ui.i18n.LanguageManagerMDB;
64  import net.sf.firemox.zone.MZone;
65  
66  import org.apache.commons.lang.StringUtils;
67  
68  /***
69   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
70   * @since 0.80
71   */
72  public class VirtualCard extends JComponent implements MouseListener, Tappable,
73  		Reversable, MouseMotionListener {
74  
75  	/***
76  	 * Creates a new instance of VirtualCard <br>
77  	 * 
78  	 * @param card
79  	 *          the real card
80  	 */
81  	public VirtualCard(MCard card) {
82  		this.card = card;
83  		setOpaque(false);
84  		addMouseListener(this);
85  		addMouseMotionListener(this);
86  		tap(false);
87  	}
88  
89  	/***
90  	 * Return the tooltip filter of this card
91  	 * 
92  	 * @return the tooltip filter of this card
93  	 */
94  	private TooltipFilter refreshToolTipFilter() {
95  		TooltipFilter privateFilter = cardInfoFilter;
96  		if (privateFilter == null) {
97  			privateFilter = TooltipFilter.fullInstance;
98  			for (TooltipFilter tooltipFilter : CardFactory.tooltipFilters) {
99  				if (tooltipFilter.suits(card)) {
100 					// the filter has been found
101 					privateFilter = tooltipFilter;
102 					break;
103 				}
104 			}
105 		}
106 		cardInfoFilter = privateFilter;
107 		return privateFilter;
108 	}
109 
110 	@SuppressWarnings("null")
111 	@Override
112 	public void paintComponent(Graphics g) {
113 		final Graphics2D g2D = (Graphics2D) g;
114 
115 		// Renderer
116 		g2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
117 				RenderingHints.VALUE_INTERPOLATION_BICUBIC);
118 		g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
119 				RenderingHints.VALUE_RENDER_QUALITY);
120 		g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
121 				RenderingHints.VALUE_ANTIALIAS_ON);
122 		g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
123 				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
124 
125 		// Optimization card painting
126 		final MZone container = card.getContainer();
127 		final boolean shortPaint = container == null
128 				|| !container.isMustBePainted(card);
129 
130 		// tap/reverse operation : PI/2, PI, -PI/2 rotation
131 		if (!shortPaint) {
132 			if (container.isMustBePaintedReversed(card)) {
133 				if (card.tapped) {
134 					g2D.translate(rotateTransformY, CardFactory.cardWidth
135 							+ rotateTransformX);
136 					g2D.rotate(angle - Math.PI / 2);
137 				} else {
138 					g2D.translate(CardFactory.cardWidth + rotateTransformX,
139 							CardFactory.cardHeight + rotateTransformY);
140 					g2D.rotate(Math.PI - angle);
141 				}
142 			} else {
143 				if (card.tapped) {
144 					g2D.translate(CardFactory.cardHeight + rotateTransformY,
145 							rotateTransformX);
146 					g2D.rotate(Math.PI / 2 + angle);
147 				} else {
148 					g2D.translate(rotateTransformX, rotateTransformY);
149 					g2D.rotate(angle);
150 				}
151 			}
152 		}
153 
154 		if (container != null) {
155 			if (container.isVisibleForOpponent() && !card.isVisibleForOpponent()
156 					&& card.isVisibleForYou()) {
157 				/*
158 				 * This card is visible for you but not for opponent in despite of the
159 				 * fact the container is public.
160 				 */
161 				g2D.drawImage(card.scaledImage(), null, null);
162 				g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
163 						0.5f));
164 				g2D.drawImage(DatabaseFactory.scaledBackImage, null, null);
165 			} else if (!card.isVisibleForYou()) {
166 				g2D.drawImage(DatabaseFactory.scaledBackImage, null, null);
167 			} else {
168 				g2D.drawImage(card.scaledImage(), null, null);
169 			}
170 		}
171 
172 		if (shortPaint)
173 			return;
174 
175 		/*
176 		 * The card picture is displayed as a rounded rectangle with 0,90,180 or
177 		 * 270°
178 		 */
179 		if (card.isHighLighted
180 				&& (card.isVisibleForYou() || StackManager.idHandedPlayer == 0)) {
181 			// Draw the rounded colored rectangle
182 			g2D.setColor(card.highLightColor);
183 			g2D.draw3DRect(0, 0, CardFactory.cardWidth - 2,
184 					CardFactory.cardHeight - 2, false);
185 		}
186 
187 		// Draw the eventual progress bar
188 		card.database.updatePaintNotification(card, g);
189 
190 		// Cursor for our pretty pictures
191 		int px = 3;
192 		int py = 3;
193 		int maxX = CardFactory.cardWidth - 2;
194 
195 		// for in-play, visible cards
196 		if (card.isVisibleForYou()
197 				|| (container != null && container.isVisibleForYou())) {
198 
199 			// draw registers above the card's picture
200 			if (refreshToolTipFilter().powerANDtoughness) {
201 				// power / toughness
202 				final String powerANDtoughness = String.valueOf(card
203 						.getValue(IdTokens.POWER))
204 						+ "/" + card.getValue(IdTokens.TOUGHNESS);
205 				g2D.setFont(g2D.getFont().deriveFont(Font.BOLD, 13));
206 				final Rectangle2D stringDim = g2D.getFontMetrics().getStringBounds(
207 						powerANDtoughness, g2D);
208 				g2D.setColor(CardFactory.powerToughnessColor);
209 				g2D.drawString(powerANDtoughness, (int) (CardFactory.cardWidth
210 						- stringDim.getWidth() - 3), CardFactory.cardHeight - 5);
211 				g2D.setColor(Color.BLUE);
212 				g2D.drawString(powerANDtoughness, (int) (CardFactory.cardWidth
213 						- stringDim.getWidth() - 3), CardFactory.cardHeight - 6);
214 			}
215 
216 			/*
217 			 * START drawing additional pictures
218 			 */
219 
220 			// draw state pictures
221 			for (StatePicture statePicture : CardFactory.statePictures) {
222 				if (px + 13 > maxX) {
223 					px = 3;
224 					py += 13;
225 				}
226 				if (statePicture.paint(card, g2D, px, py)) {
227 					px += 13;
228 				}
229 			}
230 
231 			// draw properties
232 			if (card.cachedProperties != null) {
233 				for (Integer property : card.cachedProperties) {
234 					if (Configuration.getBoolean("card.property.picture", true)
235 							&& CardFactory.propertyPictures.get(property) != null) {
236 						// There is an associated picture to this property
237 						if (px + 13 > maxX) {
238 							px = 3;
239 							py += 13;
240 						}
241 						g2D.drawImage(CardFactory.propertyPictures.get(property), px, py,
242 								12, 12, null);
243 						px += 13;
244 					}
245 				}
246 			}
247 		}
248 
249 		// draw attached objects
250 		int startX = 3;
251 		int startY = CardFactory.cardHeight - 18;
252 		if (card.registerModifiers != null) {
253 			for (int i = card.registerModifiers.length; i-- > 0;) {
254 				if (card.registerModifiers[i] != null) {
255 					startX = card.registerModifiers[i].paintObject(g, startX, startY);
256 				}
257 			}
258 		}
259 		if (card.propertyModifier != null) {
260 			startX = card.propertyModifier.paintObject(g, startX, startY);
261 		}
262 		if (card.idCardModifier != null) {
263 			card.idCardModifier.paintObject(g, startX, startY);
264 			/*
265 			 * END drawing additional pictures
266 			 */
267 		}
268 
269 		// Draw the target Id if helper said it
270 		final String id = TargetHelper.getInstance().getMyId(card);
271 		if (id != null) {
272 			if (px + 13 > maxX) {
273 				px = 3;
274 				py += 13;
275 			}
276 			if (id == TargetHelper.STR_CONTEXT1) {
277 				// TODO I am in the context 1, draw a picture
278 				g2D.setColor(Color.BLUE);
279 				g2D.setFont(g2D.getFont().deriveFont(Font.BOLD, 60 / id.length()));
280 				g2D.drawString(id, 5, CardFactory.cardHeight - 15);
281 			} else if (id == TargetHelper.STR_CONTEXT2) {
282 				// TODO I am in the context 2, draw a picture
283 				g2D.setColor(Color.BLUE);
284 				g2D.setFont(g2D.getFont().deriveFont(Font.BOLD, 60 / id.length()));
285 				g2D.drawString(id, 5, CardFactory.cardHeight - 15);
286 			} else if (id != TargetHelper.STR_SOURCE) {
287 				// } else if (id == TargetHelper.STR_SOURCE) {
288 				// TODO I am the source, draw a picture
289 				// } else {
290 				// I am a target
291 				g2D.drawImage(TargetHelper.getInstance().getTargetPictureSml(), px, py,
292 						null);
293 				py += 12;
294 			}
295 		}
296 
297 		g2D.dispose();
298 	}
299 
300 	/***
301 	 * is called when mouse is on this card, will display a preview
302 	 * 
303 	 * @param e
304 	 *          is the mouse event
305 	 * @since 0.71 art author and rules author have been added to the tooltip
306 	 */
307 	public void mouseEntered(MouseEvent e) {
308 		if (card.isVisibleForYou()) {
309 			CardFactory.previewCard.setImage(card.image(), card.getCardName());
310 			MagicUIComponents.databasePanel.revalidate(card);
311 		}
312 		setToolTipText(getTooltipString());
313 		if (card.getContainer().getZoneId() == IdZones.STACK) {
314 			TargetHelper.getInstance().addTargetedBy(StackManager.getContextOf(card));
315 		}
316 	}
317 
318 	/***
319 	 * Return HTML tooltip string of this card.
320 	 * 
321 	 * @return HTML tooltip string of this card.
322 	 */
323 	public String getTooltipString() {
324 		final StringBuilder toolTip = new StringBuilder(400);
325 
326 		// played activated ability
327 		if (card.getIdZone() == IdZones.STACK && card.isACopy()) {
328 			/*
329 			 * -> this card is not the source of ability too. Only the information of
330 			 * ability is displayed
331 			 */
332 			toolTip.append(CardFactory.ttHeaderAbility);
333 			toolTip.append(StackManager.getAbilityOf(card).toHtmlString(
334 					StackManager.getInstance().getAbilityContext()));
335 			toolTip.append(CardFactory.ttAbiltityEnd);
336 			toolTip.append(CardFactory.ttManacost);
337 			toolTip.append(StackManager.getHtmlManaCost(card));
338 			toolTip.append("(");
339 			toolTip.append(StackManager.getTotalManaPaid(card));
340 			toolTip.append(CardFactory.ttManapaid);
341 			toolTip.append(StackManager.getHtmlManaPaid(card));
342 		} else {
343 
344 			// html header and card name
345 			toolTip.append(CardFactory.ttHeader);
346 			final MZone container = card.getContainer();
347 			if (!card.isVisibleForYou()
348 					&& (container == null || !container.isVisibleForYou())) {
349 				toolTip.append("??");
350 			} else {
351 				toolTip.append(card.database.getLocalName());
352 
353 				// get the tooltip filter
354 				final TooltipFilter privateFilter = refreshToolTipFilter();
355 
356 				// card types
357 				if (privateFilter.types && card.getIdCard() != 0) {
358 					int startIndex = CardFactory.exportedIdCardValues.length;
359 					int writtenTypes = 0;
360 					StringBuilder typeSet = null;
361 					while (startIndex-- > 0 && card.getIdCard() != 0) {
362 						if (MCard.hasIdCard(card.getIdCard(),
363 								CardFactory.exportedIdCardValues[startIndex])) {
364 							if (MCard.hasIdCard(writtenTypes,
365 									CardFactory.exportedIdCardValues[startIndex])) {
366 								// is a type sets
367 								if (typeSet == null) {
368 									typeSet = new StringBuilder(30);
369 									typeSet.append(" (");
370 								} else {
371 									typeSet.append(", ");
372 								}
373 								typeSet.append(CardFactory.exportedIdCardNames[startIndex]);
374 							} else {
375 								if (writtenTypes == 0) {
376 									toolTip.append(CardFactory.ttTypes);
377 								} else {
378 									toolTip.append(", ");
379 								}
380 								toolTip.append(CardFactory.exportedIdCardNames[startIndex]);
381 								writtenTypes |= CardFactory.exportedIdCardValues[startIndex];
382 							}
383 						}
384 					}
385 					if (typeSet != null) {
386 						toolTip.append(typeSet);
387 						toolTip.append(')');
388 					}
389 				}
390 
391 				// card colors
392 				if (privateFilter.colors) {
393 					int writtenColors = 0;
394 					for (int i = IdCardColors.CARD_COLOR_VALUES.length; i-- > 1;) {
395 						if (card.hasIdColor(IdCardColors.CARD_COLOR_VALUES[i])) {
396 							if (writtenColors == 0) {
397 								toolTip.append(CardFactory.ttColors);
398 							} else {
399 								toolTip.append(", ");
400 							}
401 							toolTip.append(StringUtils.capitalize(LanguageManager
402 									.getString(IdCardColors.CARD_COLOR_NAMES[i])));
403 							writtenColors = 1;
404 						}
405 					}
406 				}
407 
408 				if (privateFilter.properties) {
409 					// card properties
410 					final Iterator<Integer> it = card.cachedProperties.iterator();
411 					if (it.hasNext()) {
412 						toolTip.append(CardFactory.ttProperties);
413 						while (it.hasNext()) {
414 							final Integer property = it.next();
415 							boolean hasProperty = false;
416 							toolTip.append("<br>&nbsp;&nbsp;&nbsp;&nbsp;");
417 
418 							if (Configuration.getBoolean("card.property.tooltip.picture",
419 									true)
420 									&& CardFactory.propertyPicturesHTML.get(property) != null) {
421 								// There is an associated picture to this property
422 								toolTip.append(CardFactory.propertyPicturesHTML.get(property));
423 								hasProperty = true;
424 							}
425 
426 							if (Configuration.getBoolean("card.property.tooltip.name", true)) {
427 								final String propertyName = CardFactory.exportedProperties
428 										.get(property);
429 								if (hasProperty)
430 									toolTip.append("&nbsp; - &nbsp;");
431 								hasProperty = true;
432 								if (propertyName == null) {
433 									// not name associated to this property
434 									toolTip.append("<font color='red'>(").append(property)
435 											.append(")</font>");
436 								} else {
437 									toolTip.append(propertyName);
438 								}
439 							}
440 							if (Configuration.getBoolean("card.property.tooltip.id", true)) {
441 								if (hasProperty)
442 									toolTip.append("&nbsp; - &nbsp;");
443 								toolTip.append(property);
444 							}
445 						}
446 					}
447 				}
448 
449 				if (privateFilter.powerANDtoughness) {
450 					// power
451 					toolTip.append(CardFactory.ttPower);
452 					toolTip.append(card.getValue(IdTokens.POWER));
453 					// toughness
454 					toolTip.append(CardFactory.ttToughness);
455 					toolTip.append(card.getValue(IdTokens.TOUGHNESS));
456 				}
457 
458 				if (card.isVisibleForYou()) {
459 					// states
460 					int value = card.getValue(IdCommonToken.STATE);
461 					if (value != 0) {
462 						int writtenStates = 0;
463 						toolTip.append(CardFactory.ttState);
464 						for (StatePicture statePicture : CardFactory.statePictures) {
465 							if (statePicture.hasState(value)) {
466 								if (writtenStates != 0) {
467 									toolTip.append(", ");
468 								}
469 								toolTip.append(LanguageManagerMDB.getString(statePicture
470 										.toString()));
471 								writtenStates = 1;
472 							}
473 						}
474 					}
475 
476 					// damages
477 					value = card.getValue(IdCommonToken.DAMAGE);
478 					if (privateFilter.damage && value > 0) {
479 						toolTip.append(CardFactory.ttDamage);
480 						toolTip.append(value);
481 					}
482 				}
483 
484 				// Display abilities
485 				if (card.getIdZone() == IdZones.STACK) {
486 					// played activated ability from this card
487 					toolTip.append(CardFactory.ttAbility);
488 					try {
489 						toolTip.append(StackManager.getAbilityOf(card).toHtmlString(
490 								StackManager.getInstance().getAbilityContext()));
491 					} catch (Throwable e) {
492 						// The is not yet in the stack now.
493 						Log.info(e);
494 						return null;
495 					}
496 					toolTip.append(CardFactory.ttAbiltityEnd);
497 					toolTip.append(CardFactory.ttManacost);
498 					toolTip.append(StackManager.getHtmlManaCost(card));
499 					toolTip.append("(");
500 					toolTip.append(StackManager.getTotalManaCost(card));
501 					toolTip.append(CardFactory.ttManapaid);
502 					toolTip.append(StackManager.getHtmlManaPaid(card));
503 				} else if (StackManager.actionManager.currentAction == WaitActivatedChoice
504 						.getInstance()) {
505 					// playable activated abilities of this card
506 					final List<Ability> list = WaitActivatedChoice.getInstance()
507 							.abilitiesOf(card);
508 					final List<Ability> advList = WaitActivatedChoice.getInstance()
509 							.advancedAbilitiesOf(card);
510 					if (list != null && !list.isEmpty()) {
511 						toolTip.append(CardFactory.ttAbility);
512 						for (Ability ability : list) {
513 							toolTip.append("<br>&nbsp;&nbsp;");
514 							toolTip.append(ability.toHtmlString(null));
515 						}
516 						toolTip.append(CardFactory.ttAbiltityEnd);
517 					}
518 
519 					// playable advanced activated abilities of this card
520 					if (advList != null && !advList.isEmpty()) {
521 						toolTip.append(CardFactory.ttAdvancedAability);
522 						for (Ability ability : advList) {
523 							toolTip.append("<br>&nbsp;&nbsp;");
524 							toolTip.append(ability.toHtmlString(null));
525 						}
526 						toolTip.append(CardFactory.ttAdvancedAabilityEnd);
527 					}
528 				}
529 
530 				// rule credits
531 				if (card.database.getRulesCredit() != null) {
532 					toolTip.append(CardFactory.ttRulesAuthor);
533 					toolTip.append(card.database.getRulesCredit());
534 				}
535 			}
536 		}
537 		// append the "this is a copy
538 		if (card.hasDirtyDataBase()) {
539 			DatabaseCard orCard = card.getOriginalDatabase();
540 			toolTip.append(TargetFactory.tooltipDirtyDataBase);
541 			toolTip.append(orCard.getLocalName());
542 		}
543 		// append the "this is [not] a valid target" text
544 		if (StackManager.isTargetMode()) {
545 			if (((ChosenTarget) StackManager.actionManager.currentAction)
546 					.isValidTarget(card)) {
547 				toolTip.append(TargetFactory.tooltipValidTarget);
548 			} else {
549 				toolTip.append(TargetFactory.tooltipInvalidTarget);
550 			}
551 		}
552 		final String id = TargetHelper.getInstance().getMyId(card);
553 		if (id != null) {
554 			if (id != TargetHelper.STR_CONTEXT1
555 			// TODO I am in the context 1, draw a picture
556 					&& id != TargetHelper.STR_CONTEXT2
557 					// TODO I am in the context 2, draw a picture
558 					&& id != TargetHelper.STR_SOURCE) {
559 				// TODO I am the source, draw a picture
560 				// I am already targeted
561 				toolTip.append("<br>");
562 				toolTip.append(TargetHelper.getInstance().getTargetPictureAsUrl());
563 			}
564 		}
565 
566 		toolTip.append("</html>");
567 		return toolTip.toString();
568 	}
569 
570 	public void mouseClicked(MouseEvent e) {
571 		card.mouseClicked(e);
572 	}
573 
574 	public void mousePressed(MouseEvent e) {
575 		if (card.getParent() instanceof MZone) {
576 			card.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
577 			((MZone) card.getParent()).startDragAndDrop(card, e.getPoint());
578 		}
579 	}
580 
581 	public void mouseDragged(MouseEvent e) {
582 		if (card.getParent() instanceof MZone
583 				&& ((MZone) card.getParent()).dragAndDropComponent == card) {
584 			isAutoAlign = false;
585 			Point mousePoint = ((MZone) card.getParent()).mousePoint;
586 			Point drag = e.getPoint();
587 			Point cardLocation = (Point) card.getLocation().clone();
588 			card.setLocation(cardLocation.x + drag.x - mousePoint.x, cardLocation.y
589 					+ drag.y - mousePoint.y);
590 		}
591 	}
592 
593 	public void mouseReleased(MouseEvent e) {
594 		card.setCursor(Cursor.getDefaultCursor());
595 		card.mouseReleased(e);
596 	}
597 
598 	public void mouseExited(MouseEvent e) {
599 		card.mouseExited(e);
600 	}
601 
602 	public void tap(boolean tapped) {
603 		card.tapped = tapped;
604 		if (tapped) {
605 			setPreferredSize(tappedSize);
606 		} else {
607 			setPreferredSize(untappedSize);
608 		}
609 		setSize(getPreferredSize());
610 	}
611 
612 	public void reverse(boolean reversed) {
613 		card.reversed = reversed;
614 	}
615 
616 	public void mouseMoved(MouseEvent e) {
617 		// nothing to do
618 	}
619 
620 	/***
621 	 * The real card
622 	 */
623 	public MCard card;
624 
625 	private TooltipFilter cardInfoFilter;
626 
627 	/***
628 	 * Is this card is aligned to the layout of container.
629 	 */
630 	public boolean isAutoAlign;
631 
632 	/***
633 	 * Reset all cached data.
634 	 */
635 	public void resetCachedData() {
636 		cardInfoFilter = null;
637 	}
638 
639 	/***
640 	 * Generate a new random angle and update the bounds.
641 	 * 
642 	 * @return true if the sizes have been updated.
643 	 */
644 	public boolean updateSizes() {
645 		Dimension oldDimension = (Dimension) getSize().clone();
646 
647 		// Generate a new random angle
648 		if (Configuration.getBoolean("randomAngle", Boolean.FALSE).booleanValue()) {
649 			angle = (new Random().nextDouble() - 0.5) * ROTATE_SCALE;
650 		} else {
651 			angle = 0;
652 		}
653 		final int cardHeight = CardFactory.cardHeight;
654 		final int cardWidth = CardFactory.cardWidth;
655 		if (angle == 0) {
656 			rotateTransformX = 0;
657 			rotateTransformY = 0;
658 
659 			// Update the bounds
660 			untappedSize = new Dimension(cardWidth, cardHeight);
661 			tappedSize = new Dimension(cardHeight, cardWidth);
662 		} else {
663 			if (angle > 0) {
664 				rotateTransformX = Math.floor(cardHeight * Math.sin(Math.abs(angle))
665 						+ 1);
666 				rotateTransformY = 0;
667 			} else {
668 				rotateTransformX = 0;
669 				rotateTransformY = Math
670 						.floor(cardWidth * Math.sin(Math.abs(angle)) + 1);
671 			}
672 
673 			// Update the bounds
674 			untappedSize = new Dimension((int) Math.floor(cardWidth * Math.cos(angle)
675 					+ cardHeight * Math.sin(Math.abs(angle)) + 3), (int) Math
676 					.floor(cardHeight * Math.cos(angle) + cardWidth
677 							* Math.sin(Math.abs(angle)) + 3));
678 			tappedSize = new Dimension((int) Math.floor(cardHeight * Math.cos(angle)
679 					+ cardWidth * Math.sin(Math.abs(angle)) + 3), (int) Math
680 					.floor(cardWidth * Math.cos(angle) + cardHeight
681 							* Math.sin(Math.abs(angle)) + 3));
682 		}
683 		final Dimension newSize;
684 		if (card.tapped) {
685 			newSize = tappedSize;
686 		} else {
687 			newSize = untappedSize;
688 		}
689 		if (!newSize.equals(oldDimension)) {
690 			setSize(newSize);
691 			setPreferredSize(newSize);
692 			return true;
693 		}
694 		return false;
695 	}
696 
697 	/***
698 	 * Update the layout of this card, and also generate a new random angle.
699 	 */
700 	public void updateLayout() {
701 		// Update the bounds
702 		if (updateSizes()) {
703 
704 			// Update the layout
705 			doLayout();
706 			Container parent = getParent();
707 			if (parent != null) {
708 				parent.doLayout();
709 				parent = parent.getParent();
710 				if (parent != null) {
711 					parent.repaint();
712 				}
713 			}
714 		}
715 	}
716 
717 	@Override
718 	public String toString() {
719 		return card.toString();
720 	}
721 
722 	/***
723 	 * Update the UI of this card. All cached data are reset.
724 	 */
725 	public void updateMUI() {
726 		cardInfoFilter = null;
727 	}
728 
729 	/***
730 	 * Angle of this card.
731 	 */
732 	private double angle = 0;
733 
734 	/***
735 	 * The bounds of card when is untapped.
736 	 */
737 	public Dimension untappedSize;
738 
739 	/***
740 	 * The bounds of card when is tapped.
741 	 */
742 	public Dimension tappedSize;
743 
744 	/***
745 	 * Translation X to do after the rotation
746 	 */
747 	private double rotateTransformX;
748 
749 	/***
750 	 * Translation Y to do after the rotation
751 	 */
752 	private double rotateTransformY;
753 }