1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
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
124 final MZone container = card.getContainer();
125 final boolean shortPaint = container == null
126 || !container.isMustBePainted(card);
127
128
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
157
158
159
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
170
171 if (card.isHighLighted
172 && (card.visibility.isVisibleForYou() || StackManager.idHandedPlayer == 0)) {
173
174 g2D.setColor(card.highLightColor);
175 g2D.draw3DRect(0, 0, CardFactory.cardWidth - 2,
176 CardFactory.cardHeight - 2, false);
177 }
178
179
180 card.database.updatePaintNotification(card, g);
181
182
183 int px = 3;
184 int py = 3;
185 int maxX = CardFactory.cardWidth - 2;
186
187
188 if (card.visibility.isVisibleForYou()) {
189
190
191 if (refreshToolTipFilter().powerANDtoughness) {
192
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
209
210
211
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
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
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
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
257
258 }
259
260
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
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
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
279
280
281
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
318 if (card.getIdZone() == IdZones.STACK && card.isACopy()) {
319
320
321
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
336 toolTip.append(CardFactory.ttHeader);
337 if (card.getPreviewImage() == DatabaseFactory.backImage) {
338 toolTip.append("??");
339 } else {
340 toolTip.append(card.database.getLocalName());
341
342
343 final TooltipFilter privateFilter = refreshToolTipFilter();
344
345
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
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
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
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> ");
408
409 if (Configuration.getBoolean("card.property.tooltip.picture",
410 true)
411 && CardFactory.propertyPicturesHTML.get(property) != null) {
412
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(" - ");
422 hasProperty = true;
423 if (propertyName == null) {
424
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(" - ");
434 toolTip.append(property);
435 }
436 }
437 }
438 }
439
440 if (privateFilter.powerANDtoughness) {
441
442 toolTip.append(CardFactory.ttPower);
443 toolTip.append(card.getValue(IdTokens.POWER));
444
445 toolTip.append(CardFactory.ttToughness);
446 toolTip.append(card.getValue(IdTokens.TOUGHNESS));
447 }
448
449 if (card.visibility.isVisibleForYou()) {
450
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
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
476 if (card.getIdZone() == IdZones.STACK) {
477
478 toolTip.append(CardFactory.ttAbility);
479 try {
480 toolTip.append(StackManager.getAbilityOf(card).toHtmlString(
481 StackManager.getInstance().getAbilityContext()));
482 } catch (Throwable e) {
483
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
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> ");
505 toolTip.append(ability.toHtmlString(null));
506 }
507 toolTip.append(CardFactory.ttAbiltityEnd);
508 }
509
510
511 if (advList != null && !advList.isEmpty()) {
512 toolTip.append(CardFactory.ttAdvancedAability);
513 for (Ability ability : advList) {
514 toolTip.append("<br> ");
515 toolTip.append(ability.toHtmlString(null));
516 }
517 toolTip.append(CardFactory.ttAdvancedAabilityEnd);
518 }
519 }
520
521
522 if (card.database.getRulesCredit() != null) {
523 toolTip.append(CardFactory.ttRulesAuthor);
524 toolTip.append(card.database.getRulesCredit());
525 }
526 }
527 }
528
529 if (card.hasDirtyDataBase()) {
530 DatabaseCard orCard = card.getOriginalDatabase();
531 toolTip.append(TargetableFactory.tooltipDirtyDataBase);
532 toolTip.append(orCard.getLocalName());
533 }
534
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
547 } else if (id == TargetHelper.STR_CONTEXT2) {
548
549 } else if (id == TargetHelper.STR_SOURCE) {
550
551 } else {
552
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
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
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
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
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
691 if (updateSizes()) {
692
693
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 }