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