1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
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
126 final MZone container = card.getContainer();
127 final boolean shortPaint = container == null
128 || !container.isMustBePainted(card);
129
130
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
159
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
177
178
179 if (card.isHighLighted
180 && (card.isVisibleForYou() || StackManager.idHandedPlayer == 0)) {
181
182 g2D.setColor(card.highLightColor);
183 g2D.draw3DRect(0, 0, CardFactory.cardWidth - 2,
184 CardFactory.cardHeight - 2, false);
185 }
186
187
188 card.database.updatePaintNotification(card, g);
189
190
191 int px = 3;
192 int py = 3;
193 int maxX = CardFactory.cardWidth - 2;
194
195
196 if (card.isVisibleForYou()
197 || (container != null && container.isVisibleForYou())) {
198
199
200 if (refreshToolTipFilter().powerANDtoughness) {
201
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
218
219
220
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
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
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
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
266
267 }
268
269
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
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
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
288
289
290
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
327 if (card.getIdZone() == IdZones.STACK && card.isACopy()) {
328
329
330
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
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
354 final TooltipFilter privateFilter = refreshToolTipFilter();
355
356
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
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
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
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> ");
417
418 if (Configuration.getBoolean("card.property.tooltip.picture",
419 true)
420 && CardFactory.propertyPicturesHTML.get(property) != null) {
421
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(" - ");
431 hasProperty = true;
432 if (propertyName == null) {
433
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(" - ");
443 toolTip.append(property);
444 }
445 }
446 }
447 }
448
449 if (privateFilter.powerANDtoughness) {
450
451 toolTip.append(CardFactory.ttPower);
452 toolTip.append(card.getValue(IdTokens.POWER));
453
454 toolTip.append(CardFactory.ttToughness);
455 toolTip.append(card.getValue(IdTokens.TOUGHNESS));
456 }
457
458 if (card.isVisibleForYou()) {
459
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
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
485 if (card.getIdZone() == IdZones.STACK) {
486
487 toolTip.append(CardFactory.ttAbility);
488 try {
489 toolTip.append(StackManager.getAbilityOf(card).toHtmlString(
490 StackManager.getInstance().getAbilityContext()));
491 } catch (Throwable e) {
492
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
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> ");
514 toolTip.append(ability.toHtmlString(null));
515 }
516 toolTip.append(CardFactory.ttAbiltityEnd);
517 }
518
519
520 if (advList != null && !advList.isEmpty()) {
521 toolTip.append(CardFactory.ttAdvancedAability);
522 for (Ability ability : advList) {
523 toolTip.append("<br> ");
524 toolTip.append(ability.toHtmlString(null));
525 }
526 toolTip.append(CardFactory.ttAdvancedAabilityEnd);
527 }
528 }
529
530
531 if (card.database.getRulesCredit() != null) {
532 toolTip.append(CardFactory.ttRulesAuthor);
533 toolTip.append(card.database.getRulesCredit());
534 }
535 }
536 }
537
538 if (card.hasDirtyDataBase()) {
539 DatabaseCard orCard = card.getOriginalDatabase();
540 toolTip.append(TargetFactory.tooltipDirtyDataBase);
541 toolTip.append(orCard.getLocalName());
542 }
543
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
556 && id != TargetHelper.STR_CONTEXT2
557
558 && id != TargetHelper.STR_SOURCE) {
559
560
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
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
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
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
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
702 if (updateSizes()) {
703
704
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 }