Moved card plugin to client. Fixed Issue 143.

This commit is contained in:
magenoxx 2011-08-15 01:36:30 +04:00
parent 0892489229
commit 14bebea64d
55 changed files with 7 additions and 90 deletions

View file

@ -22,6 +22,7 @@ import mage.view.PermanentView;
import net.xeoh.plugins.base.PluginManager;
import net.xeoh.plugins.base.impl.PluginManagerFactory;
import org.apache.log4j.Logger;
import org.mage.plugins.card.CardPluginImpl;
import javax.swing.*;
import java.awt.*;
@ -51,7 +52,7 @@ public class Plugins implements MagePlugins {
logger.info("Loading plugins...");
pm = PluginManagerFactory.createPluginManager();
pm.addPluginsFrom(new File(Constants.PLUGINS_DIRECTORY).toURI());
this.cardPlugin = pm.getPlugin(CardPlugin.class);
this.cardPlugin = new CardPluginImpl();
this.counterPlugin = pm.getPlugin(CounterPlugin.class);
this.themePlugin = pm.getPlugin(ThemePlugin.class);
logger.info("Done.");

View file

@ -0,0 +1,336 @@
package org.mage.card.arcane;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Point;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JLayeredPane;
import javax.swing.SwingUtilities;
import mage.cards.MagePermanent;
abstract public class Animation {
static private final long TARGET_MILLIS_PER_FRAME = 30;
//static private final float HALF_PI = (float)(Math.PI / 2);
static private Timer timer = new Timer("Animation", true);
//static private CardPanel delayedCardPanel;
//static private long delayedTime;
static private CardPanel enlargedCardPanel;
static private CardPanel enlargedAnimationPanel;
static private Object enlargeLock = new Object();
private TimerTask timerTask;
private FrameTimer frameTimer;
private long elapsed;
public Animation (final long duration) {
this(duration, 0);
}
public Animation (final long duration, long delay) {
timerTask = new TimerTask() {
public void run () {
if (frameTimer == null) {
start();
frameTimer = new FrameTimer();
}
elapsed += frameTimer.getTimeSinceLastFrame();
if (elapsed >= duration) {
cancel();
elapsed = duration;
}
update(elapsed / (float)duration);
if (elapsed == duration) end();
}
};
timer.scheduleAtFixedRate(timerTask, delay, TARGET_MILLIS_PER_FRAME);
}
abstract protected void update (float percentage);
protected void cancel () {
timerTask.cancel();
end();
}
protected void start () {
}
protected void end () {
}
/**
* Uses averaging of the time between the past few frames to provide smooth animation.
*/
private class FrameTimer {
static private final int SAMPLES = 6;
static private final long MAX_FRAME = 100; // Max time for one frame, to weed out spikes.
private long samples[] = new long[SAMPLES];
private int sampleIndex;
public FrameTimer () {
long currentTime = System.currentTimeMillis();
for (int i = SAMPLES - 1; i >= 0; i--)
samples[i] = currentTime - (SAMPLES - i) * TARGET_MILLIS_PER_FRAME;
}
public long getTimeSinceLastFrame () {
long currentTime = System.currentTimeMillis();
int id = sampleIndex - 1;
if (id < 0) id += SAMPLES;
long timeSinceLastSample = currentTime - samples[id];
// If the slice was too big, advance all the previous times by the diff.
if (timeSinceLastSample > MAX_FRAME) {
long diff = timeSinceLastSample - MAX_FRAME;
for (int i = 0; i < SAMPLES; i++)
samples[i] += diff;
}
long timeSinceOldestSample = currentTime - samples[sampleIndex];
samples[sampleIndex] = currentTime;
sampleIndex = (sampleIndex + 1) % SAMPLES;
return timeSinceOldestSample / (long)SAMPLES;
}
}
static public void tapCardToggle (final CardPanel panel, final MagePermanent parent, final boolean tapped, final boolean flipped) {
new Animation(300) {
protected void start () {
parent.onBeginAnimation();
}
protected void update (float percentage) {
if (tapped) {
panel.tappedAngle = CardPanel.TAPPED_ANGLE * percentage;
// reverse movement if untapping
if (!panel.isTapped()) panel.tappedAngle = CardPanel.TAPPED_ANGLE - panel.tappedAngle;
}
if (flipped) {
panel.flippedAngle = CardPanel.FLIPPED_ANGLE * percentage;
if (!panel.isFlipped()) panel.flippedAngle = CardPanel.FLIPPED_ANGLE - panel.flippedAngle;
}
panel.repaint();
}
protected void end () {
if (tapped) panel.tappedAngle = panel.isTapped() ? CardPanel.TAPPED_ANGLE : 0;
if (flipped) panel.flippedAngle = panel.isFlipped() ? CardPanel.FLIPPED_ANGLE : 0;
parent.onEndAnimation();
parent.repaint();
}
};
}
// static public void moveCardToPlay (Component source, final CardPanel dest, final CardPanel animationPanel) {
static public void moveCardToPlay (final int startX, final int startY, final int startWidth, final int endX, final int endY,
final int endWidth, final CardPanel animationPanel, final CardPanel placeholder, final JLayeredPane layeredPane,
final int speed) {
UI.invokeLater(new Runnable() {
public void run () {
final int startHeight = Math.round(startWidth * CardPanel.ASPECT_RATIO);
final int endHeight = Math.round(endWidth * CardPanel.ASPECT_RATIO);
final float a = 2f;
final float sqrta = (float)Math.sqrt(1 / a);
animationPanel.setCardBounds(startX, startY, startWidth, startHeight);
animationPanel.setAnimationPanel(true);
Container parent = animationPanel.getParent();
if (parent != layeredPane) {
layeredPane.add(animationPanel);
layeredPane.setLayer(animationPanel, JLayeredPane.MODAL_LAYER);
}
new Animation(700) {
protected void update (float percentage) {
if (placeholder != null && !placeholder.isShowing()) {
cancel();
return;
}
int currentX = startX + Math.round((endX - startX + endWidth / 2f) * percentage);
int currentY = startY + Math.round((endY - startY + endHeight / 2f) * percentage);
int currentWidth, currentHeight;
int midWidth = Math.max(200, endWidth * 2);
int midHeight = Math.round(midWidth * CardPanel.ASPECT_RATIO);
if (percentage <= 0.5f) {
percentage = percentage * 2;
float pp = sqrta * (1 - percentage);
percentage = 1 - a * pp * pp;
currentWidth = startWidth + Math.round((midWidth - startWidth) * percentage);
currentHeight = startHeight + Math.round((midHeight - startHeight) * percentage);
} else {
percentage = (percentage - 0.5f) * 2;
float pp = sqrta * percentage;
percentage = a * pp * pp;
currentWidth = midWidth + Math.round((endWidth - midWidth) * percentage);
currentHeight = midHeight + Math.round((endHeight - midHeight) * percentage);
}
currentX -= Math.round(currentWidth / 2);
currentY -= Math.round(currentHeight / 2);
animationPanel.setCardBounds(currentX, currentY, currentWidth, currentHeight);
}
protected void end () {
EventQueue.invokeLater(new Runnable() {
public void run () {
if (placeholder != null) {
placeholder.setDisplayEnabled(true);
placeholder.setImage(animationPanel);
}
animationPanel.setVisible(false);
animationPanel.repaint();
layeredPane.remove(animationPanel);
}
});
}
};
}
});
}
static public void moveCard (final int startX, final int startY, final int startWidth, final int endX, final int endY,
final int endWidth, final CardPanel animationPanel, final CardPanel placeholder, final JLayeredPane layeredPane,
final int speed) {
UI.invokeLater(new Runnable() {
public void run () {
final int startHeight = Math.round(startWidth * CardPanel.ASPECT_RATIO);
final int endHeight = Math.round(endWidth * CardPanel.ASPECT_RATIO);
animationPanel.setCardBounds(startX, startY, startWidth, startHeight);
animationPanel.setAnimationPanel(true);
Container parent = animationPanel.getParent();
if (parent != layeredPane) {
layeredPane.add(animationPanel);
layeredPane.setLayer(animationPanel, JLayeredPane.MODAL_LAYER);
}
new Animation(speed) {
protected void update (float percentage) {
int currentX = startX + Math.round((endX - startX) * percentage);
int currentY = startY + Math.round((endY - startY) * percentage);
int currentWidth = startWidth + Math.round((endWidth - startWidth) * percentage);
int currentHeight = startHeight + Math.round((endHeight - startHeight) * percentage);
animationPanel.setCardBounds(currentX, currentY, currentWidth, currentHeight);
}
protected void end () {
EventQueue.invokeLater(new Runnable() {
public void run () {
if (placeholder != null) {
placeholder.setDisplayEnabled(true);
placeholder.setImage(animationPanel);
}
animationPanel.setVisible(false);
animationPanel.repaint();
layeredPane.remove(animationPanel);
}
});
}
};
}
});
}
static public void shrinkCard () {
CardPanel enlargedCardPanel, enlargedAnimationPanel;
synchronized (enlargeLock) {
//delayedCardPanel = null;
//delayedTime = 0;
enlargedCardPanel = Animation.enlargedCardPanel;
enlargedAnimationPanel = Animation.enlargedAnimationPanel;
if (enlargedAnimationPanel == null) return;
Animation.enlargedCardPanel = null;
Animation.enlargedAnimationPanel = null;
}
final CardPanel overPanel = enlargedCardPanel, animationPanel = enlargedAnimationPanel;
animationPanel.setAnimationPanel(true);
final JLayeredPane layeredPane = SwingUtilities.getRootPane(overPanel).getLayeredPane();
layeredPane.setLayer(animationPanel, JLayeredPane.MODAL_LAYER);
final int startWidth = animationPanel.getCardWidth();
final int startHeight = Math.round(startWidth * CardPanel.ASPECT_RATIO);
final int endWidth = overPanel.getCardWidth();
final int endHeight = Math.round(endWidth * CardPanel.ASPECT_RATIO);
new Animation(200) {
protected void update (float percentage) {
int currentWidth = startWidth + Math.round((endWidth - startWidth) * percentage);
int currentHeight = startHeight + Math.round((endHeight - startHeight) * percentage);
Point startPos = SwingUtilities.convertPoint(overPanel.getParent(), overPanel.getCardLocation(), layeredPane);
int centerX = startPos.x + Math.round(endWidth / 2f);
int centerY = startPos.y + Math.round(endHeight / 2f);
int currentX = Math.max(0, centerX - Math.round(currentWidth / 2f));
currentX = Math.min(currentX, layeredPane.getWidth() - currentWidth);
int currentY = Math.max(0, centerY - Math.round(currentHeight / 2f));
currentY = Math.min(currentY, layeredPane.getHeight() - currentHeight);
animationPanel.tappedAngle = overPanel.tappedAngle * percentage;
animationPanel.setCardBounds(currentX, currentY, currentWidth, currentHeight);
}
protected void end () {
animationPanel.setVisible(false);
animationPanel.repaint();
EventQueue.invokeLater(new Runnable() {
public void run () {
layeredPane.remove(animationPanel);
}
});
}
};
}
static public boolean isShowingEnlargedCard () {
synchronized (enlargeLock) {
return enlargedAnimationPanel != null;
}
}
static public void showCard(final MagePermanent card, int count) {
if (count == 0) {
count = 1;
}
new Animation(600 / count) {
protected void start () {
}
protected void update (float percentage) {
float alpha = percentage;
card.setAlpha(alpha);
card.repaint();
}
protected void end () {
card.setAlpha(1.f);
}
};
}
static public void hideCard(final MagePermanent card, int count) {
if (count == 0) {
count = 1;
}
new Animation(600 / count) {
protected void start () {
}
protected void update (float percentage) {
float alpha = 1 - percentage;
card.setAlpha(alpha);
card.repaint();
}
protected void end () {
}
};
}
}

View file

@ -0,0 +1,764 @@
package org.mage.card.arcane;
import mage.Constants.CardType;
import mage.cards.MagePermanent;
import mage.cards.TextPopup;
import mage.cards.action.ActionCallback;
import mage.cards.action.TransferData;
import mage.components.ImagePanel;
import mage.utils.CardUtil;
import mage.view.AbilityView;
import mage.view.CardView;
import mage.view.PermanentView;
import mage.view.StackAbilityView;
import org.apache.log4j.Logger;
import org.mage.card.arcane.ScaledImagePanel.MultipassType;
import org.mage.card.arcane.ScaledImagePanel.ScalingType;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Main class for drawing Mage card object.
*
* @author arcane, nantuko
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class CardPanel extends MagePermanent implements MouseListener, MouseMotionListener {
private static final long serialVersionUID = -3272134219262184410L;
private static final Logger log = Logger.getLogger(CardPanel.class);
static public final double TAPPED_ANGLE = Math.PI / 2;
static public final double FLIPPED_ANGLE = Math.PI;
static public final float ASPECT_RATIO = 3.5f / 2.5f;
static public final int POPUP_X_GAP = 1; // prevent popup window from blinking
//static public final float ASPECT_RATIO = 1.0f;
static public CardPanel dragAnimationPanel;
public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149);
static private final float ROUNDED_CORNER_SIZE = 0.1f;
//static private final float SELECTED_BORDER_SIZE = 0.01f;
static private final float BLACK_BORDER_SIZE = 0.03f;
static private final int TEXT_GLOW_SIZE = 6;
static private final float TEXT_GLOW_INTENSITY = 3f;
static private final float rotCenterToTopCorner = 1.0295630140987000315797369464196f;
static private final float rotCenterToBottomCorner = 0.7071067811865475244008443621048f;
static private final int DEFAULT_DELAY_PERIOD = 300;
public CardView gameCard;
//public List<CardPanel> attachedPanels = new ArrayList();
private List<MagePermanent> links = new ArrayList<MagePermanent>();
public double tappedAngle = 0;
public double flippedAngle = 0;
public ScaledImagePanel imagePanel;
public ImagePanel overlayPanel;
private GlowText titleText;
private GlowText ptText;
private boolean displayEnabled = true;
private boolean isAnimationPanel;
private int cardXOffset, cardYOffset, cardWidth, cardHeight;
private boolean isSelected;
private boolean showCastingCost;
private boolean hasImage = false;
private float alpha = 1.0f;
private ActionCallback callback;
protected boolean popupShowing;
protected TextPopup popupText = new TextPopup();
protected UUID gameId;
private TransferData data = new TransferData();
private boolean isPermanent;
private boolean hasSickness;
private boolean isFoil;
private String zone;
public CardPanel(CardView newGameCard, UUID gameId, boolean loadImage, ActionCallback callback, final boolean foil) {
this.gameCard = newGameCard;
this.callback = callback;
this.gameId = gameId;
this.isPermanent = this.gameCard instanceof PermanentView;
if (isPermanent) {
this.hasSickness = ((PermanentView) this.gameCard).hasSummoningSickness();
}
//for container debug (don't remove)
//setBorder(BorderFactory.createLineBorder(Color.green));
setBackground(Color.black);
setOpaque(false);
addMouseListener(this);
addMouseMotionListener(this);
titleText = new GlowText();
setText(gameCard);
titleText.setFont(getFont().deriveFont(Font.BOLD, 13f));
titleText.setForeground(Color.white);
titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
titleText.setWrap(true);
add(titleText);
ptText = new GlowText();
if (CardUtil.isCreature(gameCard)) {
ptText.setText(gameCard.getPower() + "/" + gameCard.getToughness());
} else if (CardUtil.isPlaneswalker(gameCard)) {
ptText.setText(gameCard.getLoyalty());
}
ptText.setFont(getFont().deriveFont(Font.BOLD, 13f));
ptText.setForeground(Color.white);
ptText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
add(ptText);
BufferedImage sickness = ImageManagerImpl.getInstance().getSicknessImage();
overlayPanel = new ImagePanel(sickness, ImagePanel.SCALED);
overlayPanel.setOpaque(false);
add(overlayPanel);
imagePanel = new ScaledImagePanel();
imagePanel.setBorder(BorderFactory.createLineBorder(Color.white));
add(imagePanel);
imagePanel.setScaleLarger(true);
imagePanel.setScalingType(ScalingType.nearestNeighbor);
imagePanel.setScalingBlur(true);
imagePanel.setScalingMultiPassType(MultipassType.none);
String cardType = getType(newGameCard);
popupText.setText(getText(cardType, newGameCard));
if (!loadImage) return;
Util.threadPool.submit(new Runnable() {
public void run() {
try {
tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0;
if (gameCard.isFaceDown()) return;
BufferedImage srcImage = ImageCache.getThumbnail(gameCard);
if (srcImage != null) {
hasImage = true;
setText(gameCard);
setImage(srcImage);
setFoil(foil);
} else {
//log.warn("image wasn't found, card=" + gameCard.getName() + ", set=" + gameCard.getExpansionSetCode() + ", cid=" + gameCard.getCardNumber());
}
} catch (Exception e) {
e.printStackTrace();
} catch (Error err) {
err.printStackTrace();
}
}
});
}
private void setText(CardView card) {
if (hasImage) {
titleText.setText("");
} else {
titleText.setText(card.getName());
}
}
private void setImage(Image srcImage) {
synchronized (imagePanel) {
imagePanel.setImage(srcImage);
repaint();
}
layout();
}
public void setImage(final CardPanel panel) {
synchronized (panel.imagePanel) {
if (panel.imagePanel.hasImage()) {
setImage(panel.imagePanel.srcImage);
}
}
}
public boolean isFoil() {
return this.isFoil;
}
@Override
public void setZone(String zone) {
this.zone = zone;
}
@Override
public String getZone() {
return zone;
}
public void setFoil(boolean foil) {
this.isFoil = foil;
if (foil) {
/*BufferedImage source = BufferedImageBuilder.bufferImage(imagePanel.getSrcImage());
HueFilter filter = FilterFactory.getHueFilter();
filter.setHue(0.1f);
BufferedImage dest = filter.filter(source, null);
imagePanel.setImage(dest);
imagePanel.repaint();*/
/*
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (imagePanel.getSrcImage() == null) {
return;
}
BufferedImage source = BufferedImageBuilder.bufferImage(imagePanel.getSrcImage());
HueFilter filter = FilterFactory.getHueFilter();
float hue = 0.005f;
while (true) {
try {
Thread.sleep(DEFAULT_DELAY_PERIOD);
} catch (InterruptedException e) {
}
hue += 0.015F;
if (hue >= 1.0D) {
hue = 0.005F;
}
filter.setHue(hue);
final BufferedImage dest = filter.filter(source, null);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
imagePanel.setImage(dest);
imagePanel.repaint();
}
});
}
}
});
thread.setDaemon(false);
thread.start();
*/
}
}
public void setScalingType(ScalingType scalingType) {
imagePanel.setScalingType(scalingType);
}
public void setDisplayEnabled(boolean displayEnabled) {
this.displayEnabled = displayEnabled;
}
public boolean isDisplayEnabled() {
return displayEnabled;
}
public void setAnimationPanel(boolean isAnimationPanel) {
this.isAnimationPanel = isAnimationPanel;
}
public void setSelected(boolean isSelected) {
this.isSelected = isSelected;
repaint();
}
public void setAttacking(boolean isAttacking) {
//TODO:uncomment
//this.gameCard.setAttacking(isAttacking);
repaint();
}
public boolean getSelected() {
return this.isSelected;
}
public void setShowCastingCost(boolean showCastingCost) {
this.showCastingCost = showCastingCost;
}
public void paint(Graphics g) {
if (!displayEnabled) return;
if (!isValid()) super.validate();
Graphics2D g2d = (Graphics2D) g;
if (tappedAngle + flippedAngle > 0) {
g2d = (Graphics2D) g2d.create();
float edgeOffset = cardWidth / 2f;
g2d.rotate(tappedAngle+flippedAngle, cardXOffset + edgeOffset, cardYOffset + cardHeight - edgeOffset);
}
super.paint(g2d);
}
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (alpha != 1.0f) {
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha);
g2d.setComposite(composite);
}
//TODO:uncomment
if (!hasImage /*&& gameCard.getTableID() > 0*/) {
g2d.setColor(new Color(30, 200, 200, 120));
} else {
g2d.setColor(new Color(0, 0, 0, 200));
}
//for debug repainting
//g2d.setColor(new Color(MyRandom.random.nextInt(255),MyRandom.random.nextInt(255),MyRandom.random.nextInt(255),150));
int cornerSize = Math.max(4, Math.round(cardWidth * ROUNDED_CORNER_SIZE));
g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize);
if (isSelected) {
//g2d.setColor(new Color(0,250,0,200));
g2d.setColor(new Color(200, 120, 40, 200));
g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize);
}
//TODO:uncomment
/*
if (gameCard.isAttacking()) {
g2d.setColor(new Color(200,10,10,200));
g2d.fillRoundRect(cardXOffset+1, cardYOffset+1, cardWidth-2, cardHeight-2, cornerSize, cornerSize);
}*/
/*if (isSelected) {
g2d.setColor(Color.green);
int offset = gameCard.isTapped() ? 1 : 0;
for (int i = 1, n = Math.max(1, Math.round(cardWidth * SELECTED_BORDER_SIZE)); i <= n; i++)
g2d.drawRoundRect(cardXOffset - i, cardYOffset - i + offset, cardWidth + i * 2 - 1, cardHeight + i * 2 - 1,
cornerSize, cornerSize);
}*/
//for debugging
// REMOVEME
/*
Point component = getLocation();
int cx = getCardX() + component.x;
int cy = getCardY() + component.y;
int cw = getCardWidth();
int ch = getCardHeight();
g2d.setColor(Color.white);
g2d.drawRect(getCardX() - component.x, getCardY() - component.y, cw, ch);
*/
}
protected void paintChildren(Graphics g) {
super.paintChildren(g);
if (showCastingCost && !isAnimationPanel && cardWidth < 200 && cardWidth > 60) {
String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost());
int width = ManaSymbols.getWidth(manaCost);
if (hasImage) {
ManaSymbols.draw(g, manaCost, cardXOffset + cardWidth - width - 5, cardYOffset + 5);
} else {
ManaSymbols.draw(g, manaCost, cardXOffset + 8, cardHeight - 9);
}
}
}
public void layout() {
int borderSize = Math.round(cardWidth * BLACK_BORDER_SIZE);
imagePanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize);
imagePanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2);
if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) {
overlayPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize);
overlayPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2);
} else {
overlayPanel.setVisible(false);
}
int fontHeight = Math.round(cardHeight * (27f / 680));
boolean showText = (!isAnimationPanel && fontHeight < 12);
titleText.setVisible(showText);
ptText.setVisible(showText);
int titleX = Math.round(cardWidth * (20f / 480));
int titleY = Math.round(cardHeight * (9f / 680));
titleText.setBounds(cardXOffset + titleX, cardYOffset + titleY, cardWidth - titleX, cardHeight);
Dimension ptSize = ptText.getPreferredSize();
ptText.setSize(ptSize.width, ptSize.height);
int ptX = Math.round(cardWidth * (420f / 480)) - ptSize.width / 2;
int ptY = Math.round(cardHeight * (675f / 680)) - ptSize.height;
int offsetX = Math.round((CARD_SIZE_FULL.width - cardWidth) / 10.0f);
ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2);
if (isAnimationPanel || cardWidth < 200)
imagePanel.setScalingType(ScalingType.nearestNeighbor);
else
imagePanel.setScalingType(ScalingType.bilinear);
}
public String toString() {
return gameCard.toString();
}
@Override
public void setCardBounds(int x, int y, int width, int height) {
cardWidth = width;
cardHeight = height;
int rotCenterX = Math.round(width / 2f);
int rotCenterY = height - rotCenterX;
int rotCenterToTopCorner = Math.round(width * CardPanel.rotCenterToTopCorner);
int rotCenterToBottomCorner = Math.round(width * CardPanel.rotCenterToBottomCorner);
int xOffset = rotCenterX - rotCenterToBottomCorner;
int yOffset = rotCenterY - rotCenterToTopCorner;
cardXOffset = -xOffset;
cardYOffset = -yOffset;
width = -xOffset + rotCenterX + rotCenterToTopCorner;
height = -yOffset + rotCenterY + rotCenterToBottomCorner;
setBounds(x + xOffset, y + yOffset, width, height);
}
public void repaint() {
Rectangle b = getBounds();
JRootPane rootPane = SwingUtilities.getRootPane(this);
if (rootPane == null) return;
Point p = SwingUtilities.convertPoint(getParent(), b.x, b.y, rootPane);
rootPane.repaint(p.x, p.y, b.width, b.height);
}
public int getCardX() {
return getX() + cardXOffset;
}
public int getCardY() {
return getY() + cardYOffset;
}
public int getCardWidth() {
return cardWidth;
}
public int getCardHeight() {
return cardHeight;
}
public Point getCardLocation() {
Point p = getLocation();
p.x += cardXOffset;
p.y += cardYOffset;
return p;
}
public CardView getCard() {
return this.gameCard;
}
@Override
public void setAlpha(float alpha) {
this.alpha = alpha;
if (alpha == 0) {
this.ptText.setVisible(false);
this.titleText.setVisible(false);
} else if (alpha == 1.0f) {
this.ptText.setVisible(true);
this.titleText.setVisible(true);
}
}
public float getAlpha() {
return alpha;
}
public int getCardXOffset() {
return cardXOffset;
}
public int getCardYOffset() {
return cardYOffset;
}
public void updateImage() {
if (!hasImage) {
throw new IllegalStateException("Not implemented");
//TODO:
/*Util.threadPool.submit(new Runnable() {
public void run () {
//BufferedImage srcImage = ImageCache.getImageOriginal(gameCard);
//BufferedImage srcImage = null;
//tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
if (srcImage != null) {
hasImage = true;
setText(gameCard);
setImage(srcImage, srcImage);
}
}
});*/
}
}
@Override
public List<MagePermanent> getLinks() {
return links;
}
@Override
public boolean isTapped() {
if (isPermanent) {
return ((PermanentView) gameCard).isTapped();
}
return false;
}
@Override
public boolean isFlipped() {
if (isPermanent) {
return ((PermanentView) gameCard).isFlipped();
}
return false;
}
@Override
public void onBeginAnimation() {
}
@Override
public void onEndAnimation() {
}
@Override
public void update(CardView card) {
if (isPermanent) {
boolean needsTapping = isTapped() != ((PermanentView) card).isTapped();
boolean needsFlipping = isFlipped() != ((PermanentView) card).isFlipped();
if (needsTapping || needsFlipping) {
Animation.tapCardToggle(this, this, needsTapping, needsFlipping);
}
}
if (CardUtil.isCreature(card) && CardUtil.isPlaneswalker(card)) {
ptText.setText(card.getPower() + "/" + card.getToughness() + " (" + card.getLoyalty() + ")");
} else if (CardUtil.isCreature(card)) {
ptText.setText(card.getPower() + "/" + card.getToughness());
} else if (CardUtil.isPlaneswalker(card)) {
ptText.setText(card.getLoyalty());
} else {
ptText.setText("");
}
setText(card);
this.gameCard = card;
String cardType = getType(card);
popupText.setText(getText(cardType, card));
if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) {
overlayPanel.setVisible(true);
} else {
overlayPanel.setVisible(false);
}
repaint();
}
@Override
public boolean contains(int x, int y) {
if (containsThis(x, y, true)) return true;
/*
* if (attachedCount > 0) { for (MWCardImpl card :
* mwAttachedCards.keySet()) { if (card.contains(x, y)) return true; } }
*/
return false;
}
public boolean containsThis(int x, int y, boolean root) {
//log.info("x="+x+", y="+y);
Point component = getLocation();
//int dy = component.y;
//if (root) dy = 0;
int cx = getCardX() - component.x;
int cy = getCardY() - component.y;
int cw = getCardWidth();
int ch = getCardHeight();
if (isTapped()) {
cy = ch - cw + cx /*+ attachedDy*attachedCount*/;
ch = cw;
cw = getCardHeight();
}
//int dx = drawIcons ? 19 : 0;
//int dx = 0;
if (x >= cx && x <= cx + cw && y >= cy && y <= cy + ch) {
//log.info("!cx="+cx+", cy="+cy+", dx="+cw +", ch="+ch);
//log.info(getOriginal().getId());
return true;
} else {
//log.info("cx="+cx+", cy="+cy+", dx="+cw +", ch="+ch);
}
return false;
}
@Override
public CardView getOriginal() {
return this.gameCard;
}
@Override
public Image getImage() {
if (this.hasImage)
return ImageCache.getImageOriginal(gameCard);
return null;
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
if (gameCard.isFaceDown()) return;
if (!popupShowing) {
synchronized (this) {
if (!popupShowing) {
popupShowing = true;
callback.mouseEntered(e, getTransferDataForMouseEntered());
}
}
}
}
@Override
public void mouseDragged(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
if (gameCard.isFaceDown()) return;
data.component = this;
callback.mouseMoved(e, data);
}
@Override
public void mouseExited(MouseEvent e) {
if (gameCard.isFaceDown()) return;
if (getMousePosition(true) != null) return;
if (popupShowing) {
synchronized (this) {
if (popupShowing) {
popupShowing = false;
data.component = this;
data.card = this.gameCard;
data.popupText = popupText;
callback.mouseExited(e, data);
}
}
}
}
@Override
public void mousePressed(MouseEvent e) {
if (gameCard.isFaceDown()) return;
data.component = this;
data.card = this.gameCard;
data.gameId = this.gameId;
callback.mousePressed(e, data);
}
@Override
public void mouseReleased(MouseEvent e) {
}
/**
* Prepares data to be sent to action callback on client side.
*
* @return
*/
private TransferData getTransferDataForMouseEntered() {
data.component = this;
data.card = this.gameCard;
data.popupText = popupText;
data.gameId = this.gameId;
data.popupOffsetX = isTapped() ? cardHeight + cardXOffset + POPUP_X_GAP : cardWidth + cardXOffset + POPUP_X_GAP;
data.popupOffsetY = 40;
data.locationOnScreen = this.getLocationOnScreen();
return data;
}
protected String getType(CardView card) {
StringBuilder sbType = new StringBuilder();
for (String superType : card.getSuperTypes()) {
sbType.append(superType).append(" ");
}
for (mage.Constants.CardType cardType : card.getCardTypes()) {
sbType.append(cardType.toString()).append(" ");
}
if (card.getSubTypes().size() > 0) {
sbType.append("- ");
for (String subType : card.getSubTypes()) {
sbType.append(subType).append(" ");
}
}
return sbType.toString();
}
protected String getText(String cardType, CardView card) {
StringBuilder sb = new StringBuilder();
if (card instanceof StackAbilityView || card instanceof AbilityView) {
for (String rule : card.getRules()) {
sb.append("\n").append(rule);
}
} else {
sb.append(card.getName());
if (card.getManaCost().size() > 0) {
sb.append("\n").append(card.getManaCost());
}
sb.append("\n").append(cardType);
if (card.getColor().hasColor()) {
sb.append("\n").append(card.getColor().toString());
}
if (card.getCardTypes().contains(CardType.CREATURE)) {
sb.append("\n").append(card.getPower()).append("/").append(card.getToughness());
} else if (card.getCardTypes().contains(CardType.PLANESWALKER)) {
sb.append("\n").append(card.getLoyalty());
}
for (String rule : card.getRules()) {
sb.append("\n").append(rule);
}
if (card.getExpansionSetCode() != null && card.getExpansionSetCode().length() > 0) {
sb.append("\n").append(card.getCardNumber()).append(" - ");
//sb.append(Sets.getInstance().get(card.getExpansionSetCode()).getName()).append(" - ");
sb.append(card.getExpansionSetCode()).append(" - ");
sb.append(card.getRarity().toString());
}
}
// sb.append("\n").append(card.getId());
return sb.toString();
}
@Override
public void update(PermanentView card) {
update((CardView) card);
this.hasSickness = card.hasSummoningSickness();
}
@Override
public PermanentView getOriginalPermanent() {
throw new IllegalStateException("Is not permanent.");
}
}

View file

@ -0,0 +1,102 @@
package org.mage.card.arcane;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.BreakIterator;
import java.util.Locale;
import javax.swing.JLabel;
public class GlowText extends JLabel {
private static final long serialVersionUID = 1827677946939348001L;
private int glowSize;
@SuppressWarnings("unused")
private float glowIntensity;
private Color glowColor;
private boolean wrap;
private int lineCount = 0;
public void setGlow (Color glowColor, int size, float intensity) {
this.glowColor = glowColor;
this.glowSize = size;
this.glowIntensity = intensity;
}
public void setWrap (boolean wrap) {
this.wrap = wrap;
}
public Dimension getPreferredSize () {
Dimension size = super.getPreferredSize();
size.width += glowSize;
size.height += glowSize / 2;
return size;
}
public void setText (String text) {
super.setText(text);
}
public void paint (Graphics g) {
if (getText().length() == 0) return;
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Dimension size = getSize();
int textX = 0, textY = 0;
int wrapWidth = Math.max(0, wrap ? size.width - glowSize : Integer.MAX_VALUE);
AttributedString attributedString = new AttributedString(getText());
attributedString.addAttribute(TextAttribute.FONT, getFont());
AttributedCharacterIterator charIterator = attributedString.getIterator();
FontRenderContext fontContext = g2d.getFontRenderContext();
LineBreakMeasurer measurer = new LineBreakMeasurer(charIterator, BreakIterator.getWordInstance(Locale.ENGLISH), fontContext);
lineCount = 0;
while (measurer.getPosition() < charIterator.getEndIndex()) {
//TextLayout textLayout = measurer.nextLayout(wrapWidth);
lineCount++;
if (lineCount > 2) break;
}
charIterator.first();
// Use char wrap if word wrap would cause more than two lines of text.
if (lineCount > 2)
measurer = new LineBreakMeasurer(charIterator, BreakIterator.getCharacterInstance(Locale.ENGLISH), fontContext);
else
measurer.setPosition(0);
while (measurer.getPosition() < charIterator.getEndIndex()) {
TextLayout textLayout = measurer.nextLayout(wrapWidth);
float ascent = textLayout.getAscent();
textY += ascent; // Move down to baseline.
g2d.setColor(glowColor);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
textLayout.draw(g2d, textX + glowSize / 2 + 1, textY + glowSize / 2 - 1);
textLayout.draw(g2d, textX + glowSize / 2 + 1, textY + glowSize / 2 + 1);
textLayout.draw(g2d, textX + glowSize / 2 - 1, textY + glowSize / 2 - 1);
textLayout.draw(g2d, textX + glowSize / 2 - 1, textY + glowSize / 2 + 1);
g2d.setColor(getForeground());
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
textLayout.draw(g2d, textX + glowSize / 2, textY + glowSize / 2);
textY += textLayout.getDescent() + textLayout.getLeading(); // Move down to top of next line.
}
}
public int getLineCount() {
return this.lineCount;
}
}

View file

@ -0,0 +1,129 @@
package org.mage.card.arcane;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.utils.BufferedImageBuilder;
public class ManaSymbols {
private static final Logger log = Logger.getLogger(ManaSymbols.class);
static private final Map<String, Image> manaImages = new HashMap<String, Image>();
static private final Map<String, Image> manaImagesOriginal = new HashMap<String, Image>();
static private final Map<String, Dimension> setImagesExist = new HashMap<String, Dimension>();
static private Pattern replaceSymbolsPattern = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}");
private static final String[] sets = {"DIS", "GPT", "RAV", "MRD",
"10E", "HOP", "ALA", "CFX", "ARB", "ZEN", "WWK", "ROE", "SOM", "M10", "M11", "M12",
"MBS", "DDF", "DST", "SHM", "EVE", "APC", "NPH", "TMP", "CHK"};
static public void loadImages() {
String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG",
"BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU",
"WP", "UP", "BP", "RP", "GP", "X", "Y", "Z", "slash"};
for (String symbol : symbols) {
File file = new File(Constants.RESOURCE_PATH_MANA_MEDIUM + "/" + symbol + ".jpg");
Rectangle r = new Rectangle(11, 11);
try {
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
BufferedImage resized = ImageCache.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r);
manaImages.put(symbol, resized);
} catch (Exception e) {
}
file = new File(Constants.RESOURCE_PATH_MANA_MEDIUM + "/" + symbol + ".jpg");
try {
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
manaImagesOriginal.put(symbol, image);
} catch (Exception e) {
}
}
File file;
for (String set : sets) {
file = new File(Constants.RESOURCE_PATH_SET_SMALL);
if (!file.exists()) {
break;
}
file = new File(Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png");
try {
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
int width = image.getWidth(null);
int height = image.getHeight(null);
setImagesExist.put(set, new Dimension(width, height));
} catch (Exception e) {
}
}
}
static public Image getManaSymbolImage(String symbol) {
return manaImagesOriginal.get(symbol);
}
static public void draw(Graphics g, String manaCost, int x, int y) {
if (manaCost.length() == 0) return;
manaCost = manaCost.replace("\\", "");
manaCost = UI.getDisplayManaCost(manaCost);
StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) {
String symbol = tok.nextToken().substring(0);
Image image = manaImages.get(symbol);
if (image == null) {
//log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost);
continue;
}
g.drawImage(image, x, y, null);
x += symbol.length() > 2 ? 10 : 12; // slash.png is only 10 pixels wide.
}
}
static public String getStringManaCost(List<String> manaCost) {
StringBuilder sb = new StringBuilder();
for (String s : manaCost) {
sb.append(s);
}
return sb.toString().replace("{", "").replace("}", " ").trim();
}
static public int getWidth(String manaCost) {
int width = 0;
manaCost = manaCost.replace("\\", "");
StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) {
String symbol = tok.nextToken().substring(0);
width += symbol.length() > 2 ? 10 : 12; // slash.png is only 10 pixels wide.
}
return width;
}
static public synchronized String replaceSymbolsWithHTML(String value, boolean small) {
if (small)
return replaceSymbolsPattern.matcher(value).replaceAll("<img src='file:plugins/images/symbols/small/$1$2.jpg' alt='$1$2' width=11 height=11>");
else {
value = value.replace("{slash}", "<img src='file:plugins/images/symbols/medium/slash.jpg' alt='slash' width=10 height=13>");
return replaceSymbolsPattern.matcher(value).replaceAll("<img src='file:plugins/images/symbols/medium/$1$2.jpg' alt='$1$2' width=13 height=13>");
}
}
static public String replaceSetCodeWithHTML(String set, String rarity) {
String _set = set;
if (_set.equals("CON")) {
_set = "CFX";
}
if (setImagesExist.containsKey(_set)) {
Integer width = setImagesExist.get(_set).width;
Integer height = setImagesExist.get(_set).height;
return "<img src='file:plugins/images/sets/small/" + _set + "-" + rarity + ".png' alt='" + rarity + " ' width=" + width + " height=" + height + ">";
} else {
return set;
}
}
}

View file

@ -0,0 +1,196 @@
package org.mage.card.arcane;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class ScaledImagePanel extends JPanel {
private static final long serialVersionUID = -1523279873208605664L;
public volatile Image srcImage;
//public volatile Image srcImageBlurred;
private ScalingType scalingType = ScalingType.bilinear;
private boolean scaleLarger;
private MultipassType multiPassType = MultipassType.bilinear;
private boolean blur;
public ScaledImagePanel () {
super(false);
setOpaque(false);
}
public void setImage(Image srcImage) {
this.srcImage = srcImage;
}
public void clearImage () {
srcImage = null;
repaint();
}
public void setScalingMultiPassType (MultipassType multiPassType) {
this.multiPassType = multiPassType;
}
public void setScalingType (ScalingType scalingType) {
this.scalingType = scalingType;
}
public void setScalingBlur (boolean blur) {
this.blur = blur;
}
public void setScaleLarger (boolean scaleLarger) {
this.scaleLarger = scaleLarger;
}
public boolean hasImage () {
return srcImage != null;
}
private ScalingInfo getScalingInfo () {
int panelWidth = getWidth();
int panelHeight = getHeight();
int srcWidth = srcImage.getWidth(null);
int srcHeight = srcImage.getHeight(null);
int targetWidth = srcWidth;
int targetHeight = srcHeight;
if (scaleLarger || srcWidth > panelWidth || srcHeight > panelHeight) {
targetWidth = Math.round(panelHeight * (srcWidth / (float)srcHeight));
if (targetWidth > panelWidth) {
targetHeight = Math.round(panelWidth * (srcHeight / (float)srcWidth));
targetWidth = panelWidth;
} else
targetHeight = panelHeight;
}
ScalingInfo info = new ScalingInfo();
info.targetWidth = targetWidth;
info.targetHeight = targetHeight;
info.srcWidth = srcWidth;
info.srcHeight = srcHeight;
info.x = panelWidth / 2 - targetWidth / 2;
info.y = panelHeight / 2 - targetHeight / 2;
return info;
}
public void paint (Graphics g) {
if (srcImage == null) return;
Graphics2D g2 = (Graphics2D)g.create();
ScalingInfo info = getScalingInfo();
switch (scalingType) {
case nearestNeighbor:
scaleWithDrawImage(g2, info, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
break;
case bilinear:
scaleWithDrawImage(g2, info, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
break;
case bicubic:
scaleWithDrawImage(g2, info, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
break;
case areaAveraging:
scaleWithGetScaledInstance(g2, info, Image.SCALE_AREA_AVERAGING);
break;
case replicate:
scaleWithGetScaledInstance(g2, info, Image.SCALE_REPLICATE);
break;
}
}
private void scaleWithGetScaledInstance (Graphics2D g2, ScalingInfo info, int hints) {
Image srcImage = getSourceImage(info);
Image scaledImage = srcImage.getScaledInstance(info.targetWidth, info.targetHeight, hints);
g2.drawImage(scaledImage, info.x, info.y, null);
}
private void scaleWithDrawImage (Graphics2D g2, ScalingInfo info, Object hint) {
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
int tempDestWidth = info.srcWidth / 2, tempDestHeight = info.srcHeight / 2;
if (tempDestWidth < info.targetWidth) tempDestWidth = info.targetWidth;
if (tempDestHeight < info.targetHeight) tempDestHeight = info.targetHeight;
Image srcImage = getSourceImage(info);
// If not doing multipass or multipass only needs a single pass, just scale it once directly to the panel surface.
if (multiPassType == MultipassType.none || (tempDestWidth == info.targetWidth && tempDestHeight == info.targetHeight)) {
g2.drawImage(srcImage, info.x, info.y, info.targetWidth, info.targetHeight, null);
return;
}
BufferedImage tempImage = new BufferedImage(tempDestWidth, tempDestHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2temp = tempImage.createGraphics();
switch (multiPassType) {
case nearestNeighbor:
g2temp.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
break;
case bilinear:
g2temp.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
break;
case bicubic:
g2temp.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
break;
}
// Render first pass from image to temp.
g2temp.drawImage(srcImage, 0, 0, tempDestWidth, tempDestHeight, null);
// Render passes between the first and last pass.
int tempSrcWidth = tempDestWidth;
int tempSrcHeight = tempDestHeight;
while (true) {
if (tempDestWidth > info.targetWidth) {
tempDestWidth = tempDestWidth / 2;
if (tempDestWidth < info.targetWidth) tempDestWidth = info.targetWidth;
}
if (tempDestHeight > info.targetHeight) {
tempDestHeight = tempDestHeight / 2;
if (tempDestHeight < info.targetHeight) tempDestHeight = info.targetHeight;
}
if (tempDestWidth == info.targetWidth && tempDestHeight == info.targetHeight) break;
g2temp.drawImage(tempImage, 0, 0, tempDestWidth, tempDestHeight, 0, 0, tempSrcWidth, tempSrcHeight, null);
tempSrcWidth = tempDestWidth;
tempSrcHeight = tempDestHeight;
}
g2temp.dispose();
// Render last pass from temp to panel surface.
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(tempImage, info.x, info.y, info.x + info.targetWidth, info.y + info.targetHeight, 0, 0, tempSrcWidth,
tempSrcHeight, null);
}
private Image getSourceImage (ScalingInfo info) {
return srcImage;
//if (!blur || srcImageBlurred == null) return srcImage;
//if (info.srcWidth / 2 < info.targetWidth || info.srcHeight / 2 < info.targetHeight) return srcImage;
//return srcImageBlurred;
}
public Image getSrcImage() {
return srcImage;
}
static private class ScalingInfo {
public int targetWidth;
public int targetHeight;
public int srcWidth;
public int srcHeight;
public int x;
public int y;
}
static public enum MultipassType {
none, nearestNeighbor, bilinear, bicubic
}
static public enum ScalingType {
nearestNeighbor, replicate, bilinear, bicubic, areaAveraging
}
}

View file

@ -0,0 +1,182 @@
package org.mage.card.arcane;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Hashtable;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
import javax.swing.ViewportLayout;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.text.Element;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.ImageView;
/**
* UI utility functions.
*/
public class UI {
static private Hashtable<URL, Image> imageCache = new Hashtable<URL, Image>();
static public JToggleButton getToggleButton () {
JToggleButton button = new JToggleButton();
button.setMargin(new Insets(2, 4, 2, 4));
return button;
}
static public JButton getButton () {
JButton button = new JButton();
button.setMargin(new Insets(2, 4, 2, 4));
return button;
}
static public void setTitle (JPanel panel, String title) {
Border border = panel.getBorder();
if (border instanceof TitledBorder) {
((TitledBorder)panel.getBorder()).setTitle(title);
panel.repaint();
} else
panel.setBorder(BorderFactory.createTitledBorder(title));
}
@SuppressWarnings("deprecation")
static public URL getFileURL (String path) {
File file = new File(path);
if (file.exists()) {
try {
return file.toURL();
} catch (MalformedURLException ignored) {
}
}
return UI.class.getResource(path);
}
static public ImageIcon getImageIcon (String path) {
try {
InputStream stream;
stream = UI.class.getResourceAsStream(path);
if (stream == null && new File(path).exists()) stream = new FileInputStream(path);
if (stream == null) throw new RuntimeException("Image not found: " + path);
byte[] data = new byte[stream.available()];
stream.read(data);
return new ImageIcon(data);
} catch (IOException ex) {
throw new RuntimeException("Error reading image: " + path);
}
}
static public void setHTMLEditorKit (JEditorPane editorPane) {
editorPane.getDocument().putProperty("imageCache", imageCache); // Read internally by ImageView, but never written.
// Extend all this shit to cache images.
editorPane.setEditorKit(new HTMLEditorKit() {
private static final long serialVersionUID = -54602188235105448L;
public ViewFactory getViewFactory () {
return new HTMLFactory() {
public View create (Element elem) {
Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
if (o instanceof HTML.Tag) {
HTML.Tag kind = (HTML.Tag)o;
if (kind == HTML.Tag.IMG) return new ImageView(elem) {
public URL getImageURL () {
URL url = super.getImageURL();
// Put an image into the cache to be read by other ImageView methods.
if (url != null && imageCache.get(url) == null)
imageCache.put(url, Toolkit.getDefaultToolkit().createImage(url));
return url;
}
};
}
return super.create(elem);
}
};
}
});
}
static public void setVerticalScrollingView (JScrollPane scrollPane, final Component view) {
final JViewport viewport = new JViewport();
viewport.setLayout(new ViewportLayout() {
private static final long serialVersionUID = 7701568740313788935L;
public void layoutContainer (Container parent) {
viewport.setViewPosition(new Point(0, 0));
Dimension viewportSize = viewport.getSize();
int width = viewportSize.width;
int height = Math.max(view.getPreferredSize().height, viewportSize.height);
viewport.setViewSize(new Dimension(width, height));
}
});
viewport.setView(view);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setViewport(viewport);
}
static public String getDisplayManaCost (String manaCost) {
manaCost = manaCost.replace("/", "");
// A pipe in the cost means "process left of the pipe as the card color, but display right of the pipe as the cost".
int pipePosition = manaCost.indexOf("{|}");
if (pipePosition != -1) manaCost = manaCost.substring(pipePosition + 3);
return manaCost;
}
static public void invokeLater (Runnable runnable) {
EventQueue.invokeLater(runnable);
}
static public void invokeAndWait (Runnable runnable) {
if (EventQueue.isDispatchThread()) {
runnable.run();
return;
}
try {
EventQueue.invokeAndWait(runnable);
} catch (InterruptedException ex) {
} catch (InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
static public void setSystemLookAndFeel () {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
System.err.println("Error setting look and feel:");
ex.printStackTrace();
}
}
static public void setDefaultFont (Font font) {
for (Object key : Collections.list(UIManager.getDefaults().keys())) {
Object value = UIManager.get(key);
if (value instanceof javax.swing.plaf.FontUIResource) UIManager.put(key, font);
}
}
}

View file

@ -0,0 +1,100 @@
package org.mage.card.arcane;
import java.awt.AWTException;
import java.awt.Robot;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.Enumeration;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingUtilities;
@SuppressWarnings({ "rawtypes", "unchecked" })
public class Util {
static public final boolean isMac = System.getProperty("os.name").toLowerCase().indexOf("mac") != -1;
static public final boolean isWindows = System.getProperty("os.name").toLowerCase().indexOf("windows") == -1;
static public Robot robot;
static {
try {
new Robot();
} catch (AWTException ex) {
throw new RuntimeException("Error creating robot.", ex);
}
}
static public ThreadPoolExecutor threadPool;
static private int threadCount;
static {
threadPool = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() {
public Thread newThread (Runnable runnable) {
threadCount++;
Thread thread = new Thread(runnable, "Util" + threadCount);
thread.setDaemon(true);
return thread;
}
});
threadPool.prestartAllCoreThreads();
}
public static void broadcast (byte[] data, int port) throws IOException {
DatagramSocket socket = new DatagramSocket();
broadcast(socket, data, port, NetworkInterface.getNetworkInterfaces());
socket.close();
}
private static void broadcast (DatagramSocket socket, byte[] data, int port, Enumeration<NetworkInterface> ifaces)
throws IOException {
for (NetworkInterface iface : Collections.list(ifaces)) {
for (InetAddress address : Collections.list(iface.getInetAddresses())) {
if (!address.isSiteLocalAddress()) continue;
// Java 1.5 doesn't support getting the subnet mask, so try the two most common.
byte[] ip = address.getAddress();
ip[3] = -1; // 255.255.255.0
socket.send(new DatagramPacket(data, data.length, InetAddress.getByAddress(ip), port));
ip[2] = -1; // 255.255.0.0
socket.send(new DatagramPacket(data, data.length, InetAddress.getByAddress(ip), port));
}
}
}
static public void sleep (int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ignored) {
}
}
static public boolean classExists (String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException ex) {
return false;
}
}
static public void wait (Object lock) {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException ex) {
}
}
}
public static void invokeAndWait (Runnable runnable) {
try {
SwingUtilities.invokeAndWait(runnable);
} catch (Exception ex) {
throw new RuntimeException("Error invoking runnable in UI thread.", ex);
}
}
}

View file

@ -0,0 +1,510 @@
package org.mage.plugins.card;
import mage.cards.Card;
import mage.cards.MagePermanent;
import mage.cards.action.ActionCallback;
import mage.interfaces.plugin.CardPlugin;
import mage.utils.CardUtil;
import mage.view.CardView;
import mage.view.PermanentView;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import net.xeoh.plugins.base.annotations.events.Init;
import net.xeoh.plugins.base.annotations.events.PluginLoaded;
import net.xeoh.plugins.base.annotations.meta.Author;
import org.apache.log4j.Logger;
import org.mage.card.arcane.Animation;
import org.mage.card.arcane.CardPanel;
import org.mage.card.arcane.ManaSymbols;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.dl.DownloadGui;
import org.mage.plugins.card.dl.DownloadJob;
import org.mage.plugins.card.dl.Downloader;
import org.mage.plugins.card.dl.sources.GathererSets;
import org.mage.plugins.card.dl.sources.GathererSymbols;
import org.mage.plugins.card.images.DownloadPictures;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.info.CardInfoPaneImpl;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;
/**
* {@link CardPlugin} implementation.
*
* @author nantuko
* @version 0.1 01.11.2010 Mage permanents. Sorting card layout.
* @version 0.6 17.07.2011 #sortPermanents got option to display non-land permanents in one pile
* @version 0.7 29.07.2011 face down cards support
*/
@PluginImplementation
@Author(name = "nantuko")
public class CardPluginImpl implements CardPlugin {
private final static Logger log = Logger.getLogger(CardPluginImpl.class);
static private final int GUTTER_Y = 15;
static private final int GUTTER_X = 5;
static final float EXTRA_CARD_SPACING_X = 0.04f;
static private final float CARD_SPACING_Y = 0.03f;
static private final float STACK_SPACING_X = 0.07f;
static private final float STACK_SPACING_Y = 0.13f;
//static private final int MW_GUIDE_HEIGHT = 30;
private int landStackMax = 5;
private int cardWidthMin = 50, cardWidthMax = Constants.CARD_SIZE_FULL.width;
private boolean stackVertical = false;
private int playAreaWidth, playAreaHeight;
private int cardWidth, cardHeight;
private int extraCardSpacingX, cardSpacingX, cardSpacingY;
private int stackSpacingX, stackSpacingY;
private List<Row> rows = new ArrayList<Row>();
@Init
public void init() {
}
@PluginLoaded
public void newPlugin(CardPlugin plugin) {
ManaSymbols.loadImages();
log.info(plugin.toString() + " has been loaded.");
}
@Override
public String toString() {
return "[Card plugin, version 0.7]";
}
@Override
public MagePermanent getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage) {
boolean foil = canBeFoil && (new Random()).nextInt(5) == 0;
CardPanel cardPanel = new CardPanel(permanent, gameId, loadImage, callback, foil);
cardPanel.setCardBounds(0, 0, dimension.width, dimension.height);
boolean implemented = !permanent.getRarity().equals(mage.Constants.Rarity.NA);
cardPanel.setShowCastingCost(implemented);
return cardPanel;
}
@Override
public MagePermanent getMageCard(CardView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage) {
boolean foil = canBeFoil && (new Random()).nextInt(5) == 0;
CardPanel cardPanel = new CardPanel(permanent, gameId, loadImage, callback, foil);
cardPanel.setCardBounds(0, 0, dimension.width, dimension.height);
boolean implemented = !permanent.getRarity().equals(mage.Constants.Rarity.NA);
cardPanel.setShowCastingCost(implemented);
return cardPanel;
}
@Override
public void sortPermanents(Map<String, JComponent> ui, Collection<MagePermanent> permanents, Map<String, String> options) {
//TODO: add caching
//requires to find out is position have been changed that includes:
//adding/removing permanents, type change
if (ui == null)
throw new RuntimeException("Error: no components");
JComponent component = ui.get("jScrollPane");
JComponent component2 = ui.get("battlefieldPanel");
if (component == null)
throw new RuntimeException("Error: jScrollPane is missing");
if (component2 == null)
throw new RuntimeException("Error: battlefieldPanel is missing");
if (!(component instanceof JScrollPane))
throw new RuntimeException("Error: jScrollPane has wrong type.");
if (!(component instanceof JScrollPane))
throw new RuntimeException("Error: battlefieldPanel is missing");
JScrollPane jScrollPane = (JScrollPane) component;
JLayeredPane battlefieldPanel = (JLayeredPane) component2;
Row allLands = new Row();
outerLoop:
//
for (MagePermanent permanent : permanents) {
if (!CardUtil.isLand(permanent) || CardUtil.isCreature(permanent))
continue;
int insertIndex = -1;
// Find lands with the same name.
for (int i = 0, n = allLands.size(); i < n; i++) {
Stack stack = allLands.get(i);
MagePermanent firstPanel = stack.get(0);
if (firstPanel.getOriginal().getName().equals(permanent.getOriginal().getName())) {
if (!empty(firstPanel.getLinks())) {
// Put this land to the left of lands with the same name and attachments.
insertIndex = i;
break;
}
if (!empty(permanent.getLinks()) || stack.size() == landStackMax) {
// If this land has attachments or the stack is full, put it to the right.
insertIndex = i + 1;
continue;
}
// Add to stack.
stack.add(0, permanent);
continue outerLoop;
}
if (insertIndex != -1)
break;
}
Stack stack = new Stack();
stack.add(permanent);
allLands.add(insertIndex == -1 ? allLands.size() : insertIndex, stack);
}
Row allCreatures = new Row(permanents, RowType.creature);
Row allOthers = new Row(permanents, RowType.other);
boolean othersOnTheRight = true;
if (options != null && options.containsKey("nonLandPermanentsInOnePile")) {
if (options.get("nonLandPermanentsInOnePile").equals("true")) {
System.out.println("in one pile");
othersOnTheRight = false;
allCreatures.addAll(allOthers);
allOthers.clear();
}
}
cardWidth = cardWidthMax;
Rectangle rect = jScrollPane.getVisibleRect();
playAreaWidth = rect.width;
playAreaHeight = rect.height;
while (true) {
rows.clear();
cardHeight = Math.round(cardWidth * CardPanel.ASPECT_RATIO);
extraCardSpacingX = (int) Math.round(cardWidth * EXTRA_CARD_SPACING_X);
cardSpacingX = cardHeight - cardWidth + extraCardSpacingX;
cardSpacingY = (int) Math.round(cardHeight * CARD_SPACING_Y);
stackSpacingX = stackVertical ? 0 : (int) Math.round(cardWidth * STACK_SPACING_X);
stackSpacingY = (int) Math.round(cardHeight * STACK_SPACING_Y);
Row creatures = (Row) allCreatures.clone();
Row lands = (Row) allLands.clone();
Row others = (Row) allOthers.clone();
// Wrap all creatures and lands.
wrap(creatures, rows, -1);
int afterCreaturesIndex = rows.size();
wrap(lands, rows, afterCreaturesIndex);
// Store the current rows and others.
List<Row> storedRows = new ArrayList<Row>(rows.size());
for (Row row : rows)
storedRows.add((Row) row.clone());
Row storedOthers = (Row) others.clone();
// Fill in all rows with others.
for (Row row : rows)
fillRow(others, rows, row);
// Stop if everything fits, otherwise revert back to the stored values.
if (creatures.isEmpty() && lands.isEmpty() && others.isEmpty())
break;
rows = storedRows;
others = storedOthers;
// Try to put others on their own row(s) and fill in the rest.
wrap(others, rows, afterCreaturesIndex);
for (Row row : rows)
fillRow(others, rows, row);
// If that still doesn't fit, scale down.
if (creatures.isEmpty() && lands.isEmpty() && others.isEmpty())
break;
//cardWidth = (int)(cardWidth / 1.2);
//FIXME: -1 is too slow. why not binary search?
cardWidth--;
}
// Get size of all the rows.
int x, y = GUTTER_Y;
int maxRowWidth = 0;
for (Row row : rows) {
int rowBottom = 0;
x = GUTTER_X;
for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) {
Stack stack = row.get(stackIndex);
rowBottom = Math.max(rowBottom, y + stack.getHeight());
x += stack.getWidth();
}
y = rowBottom;
maxRowWidth = Math.max(maxRowWidth, x);
}
//setPreferredSize(new Dimension(maxRowWidth - cardSpacingX, y - cardSpacingY));
//revalidate();
// Position all card panels.
x = 0;
y = GUTTER_Y;
for (Row row : rows) {
int rowBottom = 0;
x = GUTTER_X;
for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) {
Stack stack = row.get(stackIndex);
// Align others to the right.
if (othersOnTheRight && RowType.other.isType(stack.get(0))) {
x = playAreaWidth - GUTTER_X + extraCardSpacingX;
for (int i = stackIndex, n = row.size(); i < n; i++)
x -= row.get(i).getWidth();
}
for (int panelIndex = 0, panelCount = stack.size(); panelIndex < panelCount; panelIndex++) {
MagePermanent panel = stack.get(panelIndex);
int stackPosition = panelCount - panelIndex - 1;
///setComponentZOrder((Component)panel, panelIndex);
int panelX = x + (stackPosition * stackSpacingX);
int panelY = y + (stackPosition * stackSpacingY);
//panel.setLocation(panelX, panelY);
try {
// may cause:
// java.lang.IllegalArgumentException: illegal component position 26 should be less then 26
battlefieldPanel.moveToBack(panel);
} catch (Exception e) {
e.printStackTrace();
}
panel.setCardBounds(panelX, panelY, cardWidth, cardHeight);
}
rowBottom = Math.max(rowBottom, y + stack.getHeight());
x += stack.getWidth();
}
y = rowBottom;
}
}
private boolean empty(List<?> list) {
return list == null || list.isEmpty();
}
private int wrap(Row sourceRow, List<Row> rows, int insertIndex) {
// The cards are sure to fit (with vertical scrolling) at the minimum card width.
boolean allowHeightOverflow = cardWidth == cardWidthMin;
Row currentRow = new Row();
for (int i = 0, n = sourceRow.size() - 1; i <= n; i++) {
Stack stack = sourceRow.get(i);
// If the row is not empty and this stack doesn't fit, add the row.
int rowWidth = currentRow.getWidth();
if (!currentRow.isEmpty() && rowWidth + stack.getWidth() > playAreaWidth) {
// Stop processing if the row is too wide or tall.
if (!allowHeightOverflow && rowWidth > playAreaWidth)
break;
if (!allowHeightOverflow && getRowsHeight(rows) + sourceRow.getHeight() > playAreaHeight)
break;
rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow);
currentRow = new Row();
}
currentRow.add(stack);
}
// Add the last row if it is not empty and it fits.
if (!currentRow.isEmpty()) {
int rowWidth = currentRow.getWidth();
if (allowHeightOverflow || rowWidth <= playAreaWidth) {
if (allowHeightOverflow || getRowsHeight(rows) + sourceRow.getHeight() <= playAreaHeight) {
rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow);
}
}
}
// Remove the wrapped stacks from the source row.
for (Row row : rows)
for (Stack stack : row)
sourceRow.remove(stack);
return insertIndex;
}
private void fillRow(Row sourceRow, List<Row> rows, Row row) {
int rowWidth = row.getWidth();
while (!sourceRow.isEmpty()) {
Stack stack = sourceRow.get(0);
rowWidth += stack.getWidth();
if (rowWidth > playAreaWidth)
break;
if (stack.getHeight() > row.getHeight()) {
if (getRowsHeight(rows) - row.getHeight() + stack.getHeight() > playAreaHeight)
break;
}
row.add(sourceRow.remove(0));
}
}
private int getRowsHeight(List<Row> rows) {
int height = 0;
for (Row row : rows)
height += row.getHeight();
return height - cardSpacingY + GUTTER_Y * 2;
}
static private enum RowType {
land, creature, other;
public boolean isType(MagePermanent card) {
switch (this) {
case land:
return CardUtil.isLand(card);
case creature:
return CardUtil.isCreature(card);
case other:
return !CardUtil.isLand(card) && !CardUtil.isCreature(card);
default:
throw new RuntimeException("Unhandled type: " + this);
}
}
}
private class Row extends ArrayList<Stack> {
public Row() {
super(16);
}
public Row(Collection<MagePermanent> permanents, RowType type) {
this();
addAll(permanents, type);
}
private void addAll(Collection<MagePermanent> permanents, RowType type) {
for (MagePermanent panel : permanents) {
if (!type.isType(panel)) {
continue;
}
Stack stack = new Stack();
stack.add(panel);
add(stack);
}
}
@Override
public boolean addAll(Collection<? extends Stack> c) {
boolean changed = super.addAll(c);
c.clear();
return changed;
}
private int getWidth() {
if (isEmpty())
return 0;
int width = 0;
for (Stack stack : this)
width += stack.getWidth();
return width + GUTTER_X * 2 - extraCardSpacingX;
}
private int getHeight() {
if (isEmpty())
return 0;
int height = 0;
for (Stack stack : this)
height = Math.max(height, stack.getHeight());
return height;
}
}
private class Stack extends ArrayList<MagePermanent> {
public Stack() {
super(8);
}
@Override
public boolean add(MagePermanent panel) {
boolean appended = super.add(panel);
//for (CardPanel attachedPanel : panel.attachedPanels)
//add(attachedPanel);
return appended;
}
private int getWidth() {
return cardWidth + (size() - 1) * stackSpacingX + cardSpacingX;
}
private int getHeight() {
return cardHeight + (size() - 1) * stackSpacingY + cardSpacingY;
}
}
/**
* Download images.
*
* @param allCards Set of cards to download images for.
* @param imagesPath Path to check in and store images to. Can be null, in such case default path should be used.
*/
@Override
public void downloadImages(Set<Card> allCards, String imagesPath) {
DownloadPictures.startDownload(null, allCards, imagesPath);
}
/**
* Download various symbols (mana, tap, set).
*
* @param imagesPath Path to check in and store symbols to. Can be null, in such case default path should be used.
*/
@Override
public void downloadSymbols(String imagesPath) {
final DownloadGui g = new DownloadGui(new Downloader());
Iterable<DownloadJob> it = new GathererSymbols(imagesPath);
for (DownloadJob job : it) {
g.getDownloader().add(job);
}
it = new GathererSets(imagesPath);
for(DownloadJob job:it) {
g.getDownloader().add(job);
}
JDialog d = new JDialog((Frame) null, "Download pictures", false);
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
d.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
g.getDownloader().dispose();
}
});
d.setLayout(new BorderLayout());
d.add(g);
d.pack();
d.setVisible(true);
}
@Override
public Image getManaSymbolImage(String symbol) {
return ManaSymbols.getManaSymbolImage(symbol);
}
@Override
public void onAddCard(MagePermanent card, int count) {
if (card != null) {
Animation.showCard((CardPanel) card, count > 0 ? count : 1);
try {
while ((card).getAlpha() + 0.05f < 1) {
Thread.sleep(30);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onRemoveCard(MagePermanent card, int count) {
if (card != null) {
Animation.hideCard(card, count > 0 ? count : 1);
try {
while ((card).getAlpha() - 0.05f > 0) {
Thread.sleep(30);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public JComponent getCardInfoPane() {
return new CardInfoPaneImpl();
}
@Override
public BufferedImage getOriginalImage(CardView card) {
return ImageCache.getImageOriginal(card);
}
}

View file

@ -0,0 +1,22 @@
package org.mage.plugins.card.constants;
import java.awt.Rectangle;
import java.io.File;
public class Constants {
public static final String RESOURCE_PATH_MANA_LARGE = IO.imageBaseDir + File.separator + "symbols" + File.separator + "large";
public static final String RESOURCE_PATH_MANA_MEDIUM = IO.imageBaseDir + File.separator + "symbols" + File.separator + "medium";
public static final String RESOURCE_PATH_SET = IO.imageBaseDir + File.separator + "sets" + File.separator;
public static final String RESOURCE_PATH_SET_SMALL = RESOURCE_PATH_SET + File.separator + "small" + File.separator;
public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149);
public static final Rectangle THUMBNAIL_SIZE_FULL = new Rectangle(102, 146);
public interface IO {
public static final String imageBaseDir = "plugins" + File.separator + "images";
public static final String IMAGE_PROPERTIES_FILE = "image.url.properties";
}
public static final String CARD_IMAGE_PATH_TEMPLATE = "." + File.separator + "plugins" + File.separator + "images/{set}/{name}.{collector}.full.jpg";
}

View file

@ -0,0 +1,195 @@
/**
* DownloadGui.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.BoundedRangeModel;
import javax.swing.BoxLayout;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import org.mage.plugins.card.dl.DownloadJob.State;
/**
* The class DownloadGui.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class DownloadGui extends JPanel {
private static final long serialVersionUID = -7346572382493844327L;
private final Downloader d;
private final DownloadListener l = new DownloadListener();
private final BoundedRangeModel model = new DefaultBoundedRangeModel(0, 0, 0, 0);
private final JProgressBar progress = new JProgressBar(model);
private final Map<DownloadJob, DownloadPanel> progresses = new HashMap<DownloadJob, DownloadPanel>();
private final JPanel panel = new JPanel();
public DownloadGui(Downloader d) {
super(new BorderLayout());
this.d = d;
d.addPropertyChangeListener(l);
JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createTitledBorder("Progress:"));
p.add(progress);
JButton b = new JButton("X");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getDownloader().dispose();
}
});
p.add(b, BorderLayout.EAST);
add(p, BorderLayout.NORTH);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JScrollPane pane = new JScrollPane(panel);
pane.setPreferredSize(new Dimension(500, 300));
add(pane);
for(int i = 0; i < d.getJobs().size(); i++)
addJob(i, d.getJobs().get(i));
}
public Downloader getDownloader() {
return d;
}
private class DownloadListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if(evt.getSource() instanceof DownloadJob) {
DownloadPanel p = progresses.get(evt.getSource());
if("state".equals(name)) {
if(evt.getOldValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) {
changeProgress(-1, 0);
}
if(evt.getNewValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) {
changeProgress(+1, 0);
}
if(p != null) {
p.setVisible(p.getJob().getState() != State.FINISHED);
p.revalidate();
}
} else if("message".equals(name)) {
if(p != null) {
JProgressBar bar = p.getBar();
String message = p.getJob().getMessage();
bar.setStringPainted(message != null);
bar.setString(message);
}
}
} else if(evt.getSource() == d) {
if("jobs".equals(name)) {
IndexedPropertyChangeEvent ev = (IndexedPropertyChangeEvent) evt;
int index = ev.getIndex();
DownloadJob oldValue = (DownloadJob) ev.getOldValue();
if(oldValue != null) removeJob(index, oldValue);
DownloadJob newValue = (DownloadJob) ev.getNewValue();
if(newValue != null) addJob(index, newValue);
}
}
}
}
private synchronized void addJob(int index, DownloadJob job) {
job.addPropertyChangeListener(l);
changeProgress(0, +1);
DownloadPanel p = new DownloadPanel(job);
progresses.put(job, p);
panel.add(p, index);
panel.revalidate();
}
private synchronized void removeJob(int index, DownloadJob job) {
assert progresses.get(job) == panel.getComponent(index);
job.removePropertyChangeListener(l);
changeProgress(0, -1);
progresses.remove(job);
panel.remove(index);
panel.revalidate();
}
private synchronized void changeProgress(int progress, int total) {
progress += model.getValue();
total += model.getMaximum();
model.setMaximum(total);
model.setValue(progress);
this.progress.setStringPainted(true);
this.progress.setString(progress + "/" + total);
}
private class DownloadPanel extends JPanel {
private static final long serialVersionUID = 1187986738303477168L;
private DownloadJob job;
private JProgressBar bar;
public DownloadPanel(DownloadJob job) {
super(new BorderLayout());
this.job = job;
setBorder(BorderFactory.createTitledBorder(job.getName()));
add(bar = new JProgressBar(job.getProgress()));
JButton b = new JButton("X");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
switch(getJob().getState()) {
case NEW:
case WORKING:
getJob().setState(State.ABORTED);
}
}
});
add(b, BorderLayout.EAST);
if(job.getState() == State.FINISHED | job.getState() == State.ABORTED) {
changeProgress(+1, 0);
}
setVisible(job.getState() != State.FINISHED);
String message = job.getMessage();
bar.setStringPainted(message != null);
bar.setString(message);
Dimension d = getPreferredSize();
d.width = Integer.MAX_VALUE;
setMaximumSize(d);
// d.width = 500;
// setMinimumSize(d);
}
public DownloadJob getJob() {
return job;
}
public JProgressBar getBar() {
return bar;
}
}
}

View file

@ -0,0 +1,213 @@
/**
* DownloadJob.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import org.mage.plugins.card.dl.beans.properties.Property;
import org.mage.plugins.card.dl.lm.AbstractLaternaBean;
/**
* The class DownloadJob.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class DownloadJob extends AbstractLaternaBean {
public static enum State {
NEW, WORKING, FINISHED, ABORTED;
}
private final String name;
private final Source source;
private final Destination destination;
private final Property<State> state = properties.property("state", State.NEW);
private final Property<String> message = properties.property("message");
private final Property<Exception> error = properties.property("error");
private final BoundedRangeModel progress = new DefaultBoundedRangeModel();
public DownloadJob(String name, Source source, Destination destination) {
this.name = name;
this.source = source;
this.destination = destination;
}
/**
* Sets the job's state. If the state is {@link State#ABORTED}, it instead sets the error to "ABORTED"
*/
public void setState(State state) {
if(state == State.ABORTED) setError("ABORTED");
else this.state.setValue(state);
}
/**
* Sets the job's state to {@link State#ABORTED} and the error message to the given message. Logs a warning
* with the given message.
*/
public void setError(String message) {
setError(message, null);
}
/**
* Sets the job's state to {@link State#ABORTED} and the error to the given exception. Logs a warning with the
* given exception.
*/
public void setError(Exception error) {
setError(null, error);
}
/**
* Sets the job's state to {@link State#ABORTED} and the error to the given exception. Logs a warning with the
* given message and exception.
*/
public void setError(String message, Exception error) {
if(message == null) message = error.toString();
log.warn(message, error);
this.state.setValue(State.ABORTED);
this.error.setValue(error);
this.message.setValue(message);
}
/**
* Sets the job's message.
*/
public void setMessage(String message) {
this.message.setValue(message);
}
public BoundedRangeModel getProgress() {
return progress;
}
public State getState() {
return state.getValue();
}
public Exception getError() {
return error.getValue();
}
public String getMessage() {
return message.getValue();
}
public String getName() {
return name;
}
public Source getSource() {
return source;
}
public Destination getDestination() {
return destination;
}
public static Source fromURL(final String url) {
return fromURL(null, url);
}
public static Source fromURL(final URL url) {
return fromURL(null, url);
}
public static Source fromURL(final Proxy proxy, final String url) {
return new Source() {
private URLConnection c;
public URLConnection getConnection() throws IOException {
if(c == null) c = proxy == null? new URL(url).openConnection():new URL(url).openConnection(proxy);
return c;
}
@Override
public InputStream open() throws IOException {
return getConnection().getInputStream();
}
@Override
public int length() throws IOException {
return getConnection().getContentLength();
}
};
}
public static Source fromURL(final Proxy proxy, final URL url) {
return new Source() {
private URLConnection c;
public URLConnection getConnection() throws IOException {
if(c == null) c = proxy == null? url.openConnection():url.openConnection(proxy);
return c;
}
@Override
public InputStream open() throws IOException {
return getConnection().getInputStream();
}
@Override
public int length() throws IOException {
return getConnection().getContentLength();
}
};
}
public static Destination toFile(final String file) {
return toFile(new File(file));
}
public static Destination toFile(final File file) {
return new Destination() {
@Override
public OutputStream open() throws IOException {
File parent = file.getAbsoluteFile().getParentFile();
//Trying to create the directory before checking it exists makes it threadsafe
if(!parent.mkdirs() && !parent.exists()) throw new IOException(parent
+ ": directory could not be created");
return new FileOutputStream(file);
}
@Override
public boolean exists() {
return file.isFile();
}
@Override
public void delete() throws IOException {
if(file.exists() && !file.delete()) throw new IOException(file + " couldn't be deleted");
}
};
}
public static interface Source {
public InputStream open() throws IOException;
public int length() throws IOException;
}
public static interface Destination {
public OutputStream open() throws IOException;
public boolean exists() throws IOException;
public void delete() throws IOException;
}
}

View file

@ -0,0 +1,157 @@
/**
* Downloader.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.BoundedRangeModel;
import org.apache.log4j.Logger;
import org.jetlang.channels.Channel;
import org.jetlang.channels.MemoryChannel;
import org.jetlang.core.Callback;
import org.jetlang.core.Disposable;
import org.jetlang.fibers.Fiber;
import org.jetlang.fibers.PoolFiberFactory;
import org.mage.plugins.card.dl.DownloadJob.Destination;
import org.mage.plugins.card.dl.DownloadJob.Source;
import org.mage.plugins.card.dl.DownloadJob.State;
import org.mage.plugins.card.dl.lm.AbstractLaternaBean;
/**
* The class Downloader.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class Downloader extends AbstractLaternaBean implements Disposable {
private static final Logger log = Logger.getLogger(Downloader.class);
private final List<DownloadJob> jobs = properties.list("jobs");
private final Channel<DownloadJob> channel = new MemoryChannel<DownloadJob>();
private final ExecutorService pool = Executors.newCachedThreadPool();
private final List<Fiber> fibers = new ArrayList<Fiber>();
public Downloader() {
PoolFiberFactory f = new PoolFiberFactory(pool);
//subscribe multiple fibers for parallel execution
for(int i = 0, numThreads = 10; i < numThreads; i++) {
Fiber fiber = f.create();
fiber.start();
fibers.add(fiber);
channel.subscribe(fiber, new DownloadCallback());
}
}
@Override
public void dispose() {
for(DownloadJob j:getJobs()) {
switch(j.getState()) {
case NEW:
case WORKING:
j.setState(State.ABORTED);
}
}
for(Fiber f:fibers)
f.dispose();
pool.shutdown();
}
@Override
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
public void add(DownloadJob job) {
if(job.getState() == State.WORKING) throw new IllegalArgumentException("Job already running");
if(job.getState() == State.FINISHED) throw new IllegalArgumentException("Job already finished");
job.setState(State.NEW);
jobs.add(job);
channel.publish(job);
}
public List<DownloadJob> getJobs() {
return jobs;
}
/**
* Performs the download job: Transfers data from {@link Source} to {@link Destination} and updates the
* download job's state to reflect the progress.
*/
private class DownloadCallback implements Callback<DownloadJob> {
@Override
public void onMessage(DownloadJob job) {
//the job won't be processed by multiple threads
synchronized(job) {
if(job.getState() != State.NEW) return;
job.setState(State.WORKING);
}
try {
Source src = job.getSource();
Destination dst = job.getDestination();
BoundedRangeModel progress = job.getProgress();
if(dst.exists()) {
progress.setMaximum(1);
progress.setValue(1);
} else {
progress.setMaximum(src.length());
InputStream is = new BufferedInputStream(src.open());
try {
OutputStream os = new BufferedOutputStream(dst.open());
try {
byte[] buf = new byte[8 * 1024];
int total = 0;
for(int len; (len = is.read(buf)) != -1;) {
if(job.getState() == State.ABORTED) throw new IOException("Job was aborted");
progress.setValue(total += len);
os.write(buf, 0, len);
}
} catch(IOException ex) {
try {
dst.delete();
} catch(IOException ex1) {
log.warn("While deleting", ex1);
}
throw ex;
} finally {
try {
os.close();
} catch(IOException ex) {
log.warn("While closing", ex);
}
}
} finally {
try {
is.close();
} catch(IOException ex) {
log.warn("While closing", ex);
}
}
}
job.setState(State.FINISHED);
} catch(IOException ex) {
job.setError(ex);
}
}
}
}

View file

@ -0,0 +1,37 @@
/**
* AbstractBoundBean.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans;
import java.beans.PropertyChangeListener;
/**
* The class AbstractBoundBean.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public abstract class AbstractBoundBean implements BoundBean {
protected PropertyChangeSupport s = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
s.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
s.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
s.removePropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
s.removePropertyChangeListener(propertyName, listener);
}
}

View file

@ -0,0 +1,27 @@
/**
* BoundBean.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans;
import java.beans.PropertyChangeListener;
/**
* The class BoundBean.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public interface BoundBean {
public void addPropertyChangeListener(PropertyChangeListener listener);
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener);
public void removePropertyChangeListener(PropertyChangeListener listener);
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener);
}

View file

@ -0,0 +1,121 @@
/**
* EventListenerList.java
*
* Created on 08.04.2010
*/
package org.mage.plugins.card.dl.beans;
import static java.util.Arrays.*;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* The class EventListenerList.
*
* @version V0.0 08.04.2010
* @author Clemens Koza
*/
public class EventListenerList extends javax.swing.event.EventListenerList {
private static final long serialVersionUID = -7545754245081842909L;
/**
* Returns an iterable over all listeners for the specified classes. the listener classes are in the specified
* order. for every class, listeners are in the reverse order of registering. A listener contained multiple
* times (for a single or multiple classes) is only returned the first time it occurs.
*/
public <T extends EventListener> Iterable<T> getIterable(final Class<? extends T>... listenerClass) {
//transform class -> iterable
List<Iterable<T>> l = Lists.transform(asList(listenerClass), new ClassToIterableFunction<T>());
//compose iterable (use an arraylist to memoize the function's results)
final Iterable<T> it = Iterables.concat(new ArrayList<Iterable<T>>(l));
//transform to singleton iterators
return new Iterable<T>() {
public Iterator<T> iterator() {
return new SingletonIterator<T>(it.iterator());
}
};
}
/**
* Returns an iterator over all listeners for the specified classes. the listener classes are in the specified
* order. for every class, listeners are in the reverse order of registering. A listener contained multiple
* times (for a single or multiple classes) is only returned the first time it occurs.
*/
public <T extends EventListener> Iterator<T> getIterator(Class<? extends T>... listenerClass) {
return getIterable(listenerClass).iterator();
}
/**
* Iterates backwards over the listeners registered for a class by using the original array. The Listener runs
* backwards, just as listener notification usually works.
*/
private class ListenerIterator<T> extends AbstractIterator<T> {
private final Class<? extends T> listenerClass;
private Object[] listeners = listenerList;
private int index = listeners.length;
private ListenerIterator(Class<? extends T> listenerClass) {
this.listenerClass = listenerClass;
}
@Override
@SuppressWarnings("unchecked")
protected T computeNext() {
for(index -= 2; index >= 0; index -= 2) {
if(listenerClass == listeners[index]) return (T) listeners[index + 1];
}
return endOfData();
}
}
/**
* Transforms a class to the associated listener iterator
*/
private class ClassToIterableFunction<T> implements Function<Class<? extends T>, Iterable<T>> {
public Iterable<T> apply(final Class<? extends T> from) {
return new Iterable<T>() {
public Iterator<T> iterator() {
return new ListenerIterator<T>(from);
}
};
}
}
/**
* Filters the delegate iterator so that every but the first occurrence of every element is ignored.
*/
private static class SingletonIterator<T> extends AbstractIterator<T> {
private Iterator<T> it;
private HashSet<T> previous = new HashSet<T>();
public SingletonIterator(Iterator<T> it) {
this.it = it;
}
@Override
protected T computeNext() {
while(it.hasNext()) {
T next = it.next();
if(previous.add(next)) return next;
}
return endOfData();
}
}
}

View file

@ -0,0 +1,31 @@
/**
* PropertyChangeSupport.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans;
/**
* The class PropertyChangeSupport.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeSupport extends java.beans.PropertyChangeSupport {
private static final long serialVersionUID = -4241465377828790076L;
private Object sourceBean;
public PropertyChangeSupport(Object sourceBean) {
super(sourceBean);
this.sourceBean = sourceBean;
}
public Object getSourceBean() {
return sourceBean;
}
}

View file

@ -0,0 +1,360 @@
/**
* ListenableCollections.java
*
* Created on 25.04.2010
*/
package org.mage.plugins.card.dl.beans.collections;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSequentialList;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
/**
* The class ListenableCollections supports applications that need to listen for modifications on different
* collections. Unlike most listener models, a listenable collection does not have public methods for adding or
* removing listeners. Instead, the wrapping methods take exactly one listener, because listening to collections is
* low-level. That single listener will usually manage multiple high-level listeners.
*
* @version V0.0 25.04.2010
* @author Clemens Koza
*/
public final class ListenableCollections {
private ListenableCollections() {}
public static <E> List<E> listenableList(List<E> list, ListListener<E> listener) {
if(list instanceof RandomAccess) return new ListenableList<E>(list, listener);
else return new ListenableSequentialList<E>(list, listener);
}
public static <E> Set<E> listenableSet(Set<E> set, SetListener<E> listener) {
return new ListenableSet<E>(set, listener);
}
public static <K, V> Map<K, V> listenableMap(Map<K, V> map, MapListener<K, V> listener) {
return new ListenableMap<K, V>(map, listener);
}
public static interface ListListener<E> extends Serializable {
/**
* Notified after a value was added to the list.
*/
public void add(int index, E newValue);
/**
* Notified after a value in the list was changed.
*/
public void set(int index, E oldValue, E newValue);
/**
* Notified after a value was removed from the list.
*/
public void remove(int index, E oldValue);
}
private static class ListenableList<E> extends AbstractList<E> implements RandomAccess, Serializable {
private static final long serialVersionUID = 8622608480525537692L;
private List<E> delegate;
private ListListener<E> listener;
public ListenableList(List<E> delegate, ListListener<E> listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override
public void add(int index, E e) {
delegate.add(index, e);
listener.add(index, e);
}
@Override
public E set(int index, E element) {
E e = delegate.set(index, element);
listener.set(index, e, element);
return e;
}
@Override
public E remove(int index) {
E e = delegate.remove(index);
listener.remove(index, e);
return e;
}
@Override
public E get(int index) {
return delegate.get(index);
}
@Override
public int size() {
return delegate.size();
}
}
private static class ListenableSequentialList<E> extends AbstractSequentialList<E> implements Serializable {
private static final long serialVersionUID = 3630474556578001885L;
private List<E> delegate;
private ListListener<E> listener;
public ListenableSequentialList(List<E> delegate, ListListener<E> listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override
public ListIterator<E> listIterator(final int index) {
return new ListIterator<E>() {
private final ListIterator<E> it = delegate.listIterator(index);
private int lastIndex;
private E lastValue;
public boolean hasNext() {
return it.hasNext();
}
public boolean hasPrevious() {
return it.hasPrevious();
}
public E next() {
lastIndex = it.nextIndex();
lastValue = it.next();
return lastValue;
}
public int nextIndex() {
return it.nextIndex();
}
public E previous() {
lastIndex = it.previousIndex();
lastValue = it.previous();
return lastValue;
}
public int previousIndex() {
return it.previousIndex();
}
public void add(E o) {
it.add(o);
listener.add(previousIndex(), o);
}
public void set(E o) {
it.set(o);
listener.set(lastIndex, lastValue, o);
}
public void remove() {
it.remove();
listener.remove(lastIndex, lastValue);
}
};
}
@Override
public int size() {
return delegate.size();
}
}
public static interface SetListener<E> extends Serializable {
/**
* Notified after a value was added to the set.
*/
public void add(E newValue);
/**
* Notified after a value was removed from the set.
*/
public void remove(E oldValue);
}
private static class ListenableSet<E> extends AbstractSet<E> implements Serializable {
private static final long serialVersionUID = 7728087988927063221L;
private Set<E> delegate;
private SetListener<E> listener;
public ListenableSet(Set<E> set, SetListener<E> listener) {
delegate = set;
this.listener = listener;
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@Override
public boolean add(E o) {
boolean b = delegate.add(o);
if(b) listener.add(o);
return b;
};
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object o) {
boolean b = delegate.remove(o);
if(b) listener.remove((E) o);
return b;
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
private final Iterator<E> it = delegate.iterator();
private boolean hasLast;
private E last;
public boolean hasNext() {
return it.hasNext();
}
public E next() {
last = it.next();
hasLast = true;
return last;
}
public void remove() {
if(!hasLast) throw new IllegalStateException();
it.remove();
listener.remove(last);
}
};
}
@Override
public int size() {
return delegate.size();
}
}
public static interface MapListener<K, V> extends Serializable {
/**
* Notified after a value was put into the map.
*/
public void put(K key, V newValue);
/**
* Notified after a value in the map was changed.
*/
public void set(K key, V oldValue, V newValue);
/**
* Notified after a value was removed from the map.
*/
public void remove(K key, V oldValue);
}
private static class ListenableMap<K, V> extends AbstractMap<K, V> implements Serializable {
private static final long serialVersionUID = 4032087477448965103L;
private Map<K, V> delegate;
private MapListener<K, V> listener;
private Set<Entry<K, V>> entrySet;
public ListenableMap(Map<K, V> map, MapListener<K, V> listener) {
this.listener = listener;
delegate = map;
entrySet = new EntrySet();
}
@Override
public V put(K key, V value) {
if(delegate.containsKey(key)) {
V old = delegate.put(key, value);
listener.set(key, old, value);
return old;
} else {
delegate.put(key, value);
listener.put(key, value);
return null;
}
}
@SuppressWarnings("unchecked")
@Override
public V remove(Object key) {
if(delegate.containsKey(key)) {
V old = delegate.remove(key);
listener.remove((K) key, old);
return old;
} else return null;
}
@Override
public V get(Object key) {
return delegate.get(key);
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return entrySet;
}
private final class EntrySet extends AbstractSet<Entry<K, V>> implements Serializable {
private static final long serialVersionUID = -780485106953107075L;
private final Set<Entry<K, V>> delegate = ListenableMap.this.delegate.entrySet();
@Override
public int size() {
return delegate.size();
}
@Override
public Iterator<Entry<K, V>> iterator() {
return new Iterator<Entry<K, V>>() {
private final Iterator<Entry<K, V>> it = delegate.iterator();
private boolean hasLast;
private Entry<K, V> last;
public boolean hasNext() {
return it.hasNext();
}
public Entry<K, V> next() {
last = it.next();
hasLast = true;
return last;
}
public void remove() {
if(!hasLast) throw new IllegalStateException();
hasLast = false;
it.remove();
listener.remove(last.getKey(), last.getValue());
}
};
}
}
}
}

View file

@ -0,0 +1,17 @@
/**
* This package contains bean-related utilities, most importantly
* <ul>
* <li>{@link net.slightlymagic.beans.BoundBean} and {@link net.slightlymagic.beans.AbstractBoundBean}, an interface and an
* abstract class for easier property change support for bean classes</li>
* <li>{@link net.slightlymagic.beans.properties.Properties} and its implementations. These make it easy for beans
* to use delegates, by providing a centralized configuration for bean properties. For example, a {@link
* net.slightlymagic.beans.properties.bound.BoundProperties} object creates properties that do automatic property
* change notifications. What exact configuration is used is thus hidden from the delegate.</li>
* <li>The {@link net.slightlymagic.beans.relational.Relations} class provides relational properties that model
* bidirectional 1:1, 1:n and m:n relationships.</li>
* </ul>
*/
package org.mage.plugins.card.dl.beans;

View file

@ -0,0 +1,47 @@
/**
* AbstractProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.properties.basic.BasicProperty;
/**
* The class AbstractProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public abstract class AbstractProperties implements Properties {
public <T> Property<T> property(String name, T value) {
return property(name, new BasicProperty<T>(value));
}
public <T> Property<T> property(String name) {
return property(name, new BasicProperty<T>());
}
public <E> List<E> list(String name) {
return list(name, new ArrayList<E>());
}
public <E> Set<E> set(String name) {
return set(name, new HashSet<E>());
}
public <K, V> Map<K, V> map(String name) {
return map(name, new HashMap<K, V>());
}
}

View file

@ -0,0 +1,38 @@
/**
* AbstractProperty.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import static java.lang.String.*;
/**
* The class AbstractProperty.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public abstract class AbstractProperty<T> implements Property<T> {
@Override
public int hashCode() {
T value = getValue();
return value == null? 0:value.hashCode();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Property<?>)) return false;
Object value = getValue();
Object other = ((Property<?>) obj).getValue();
return value == other || (value != null && value.equals(other));
}
@Override
public String toString() {
return valueOf(getValue());
}
}

View file

@ -0,0 +1,61 @@
/**
* CompoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import static java.util.Arrays.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The class CompoundProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public class CompoundProperties extends AbstractProperties {
private List<Properties> delegates;
public CompoundProperties(Properties... delegates) {
this.delegates = asList(delegates);
Collections.reverse(this.delegates);
}
public CompoundProperties(List<Properties> delegates) {
this.delegates = new ArrayList<Properties>(delegates);
Collections.reverse(this.delegates);
}
public <T> Property<T> property(String name, Property<T> property) {
for(Properties p:delegates)
property = p.property(name, property);
return property;
}
public <E> List<E> list(String name, List<E> list) {
for(Properties p:delegates)
list = p.list(name, list);
return list;
}
public <E> Set<E> set(String name, Set<E> set) {
for(Properties p:delegates)
set = p.set(name, set);
return set;
}
public <K, V> Map<K, V> map(String name, Map<K, V> map) {
for(Properties p:delegates)
map = p.map(name, map);
return map;
}
}

View file

@ -0,0 +1,44 @@
/**
* Properties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.properties.bound.BoundProperties;
/**
* The class Properties. A Properties object is a factory for bean properties. For example, the
* {@link BoundProperties} class produces properties that can be observed using {@link PropertyChangeListener}s.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public interface Properties {
public <T> Property<T> property(String name, Property<T> property);
public <E> List<E> list(String name, List<E> list);
public <E> Set<E> set(String name, Set<E> set);
public <K, V> Map<K, V> map(String name, Map<K, V> map);
public <T> Property<T> property(String name, T value);
public <T> Property<T> property(String name);
public <E> List<E> list(String name);
public <E> Set<E> set(String name);
public <K, V> Map<K, V> map(String name);
}

View file

@ -0,0 +1,32 @@
/**
* Property.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
/**
* The class Property.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public interface Property<T> {
public void setValue(T value);
public T getValue();
/**
* A property's hash code is its value's hashCode, or {@code null} if the value is null.
*/
@Override
public int hashCode();
/**
* Two properties are equal if their values are equal.
*/
@Override
public boolean equals(Object obj);
}

View file

@ -0,0 +1,40 @@
/**
* BoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties.basic;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.properties.AbstractProperties;
import org.mage.plugins.card.dl.beans.properties.Property;
/**
* The class BoundProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public class BasicProperties extends AbstractProperties {
public <T> Property<T> property(String name, Property<T> value) {
return value;
}
public <E> List<E> list(String name, List<E> list) {
return list;
}
public <E> Set<E> set(String name, Set<E> set) {
return set;
}
public <K, V> Map<K, V> map(String name, Map<K, V> map) {
return map;
}
}

View file

@ -0,0 +1,37 @@
/**
* BasicProperty.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.basic;
import org.mage.plugins.card.dl.beans.properties.AbstractProperty;
/**
* The class BasicProperty.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class BasicProperty<T> extends AbstractProperty<T> {
private T value;
public BasicProperty() {
this(null);
}
public BasicProperty(T initialValue) {
value = initialValue;
}
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}

View file

@ -0,0 +1,56 @@
/**
* BoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import static org.mage.plugins.card.dl.beans.collections.ListenableCollections.listenableList;
import static org.mage.plugins.card.dl.beans.collections.ListenableCollections.listenableMap;
import static org.mage.plugins.card.dl.beans.collections.ListenableCollections.listenableSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.properties.AbstractProperties;
import org.mage.plugins.card.dl.beans.properties.Property;
/**
* The class BoundProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public class BoundProperties extends AbstractProperties {
public final PropertyChangeSupport s;
public BoundProperties(Object sourceBean) {
this(new PropertyChangeSupport(sourceBean));
}
public BoundProperties(PropertyChangeSupport s) {
if(s == null) throw new IllegalArgumentException("s == null");
this.s = s;
}
public <T> Property<T> property(String name, Property<T> property) {
return new BoundProperty<T>(s, name, property);
}
public <E> List<E> list(String name, List<E> list) {
return listenableList(list, new PropertyChangeListListener<E>(s, name));
}
public <E> Set<E> set(String name, Set<E> set) {
return listenableSet(set, new PropertyChangeSetListener<E>(s, set, name));
}
public <K, V> Map<K, V> map(String name, Map<K, V> map) {
return listenableMap(map, new PropertyChangeMapListener<K, V>(s, map, name));
}
}

View file

@ -0,0 +1,44 @@
/**
* BasicProperty.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import java.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.properties.AbstractProperty;
import org.mage.plugins.card.dl.beans.properties.Property;
/**
* The class BasicProperty.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class BoundProperty<T> extends AbstractProperty<T> {
private PropertyChangeSupport s;
private String name;
private Property<T> property;
public BoundProperty(PropertyChangeSupport s, String name, Property<T> property) {
if(property == null) throw new IllegalArgumentException();
this.s = s;
this.name = name;
this.property = property;
}
public void setValue(T value) {
T old = getValue();
property.setValue(value);
s.firePropertyChange(name, old, getValue());
}
public T getValue() {
return property.getValue();
}
}

View file

@ -0,0 +1,43 @@
/**
* PropertyChangeListListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.collections.ListenableCollections.ListListener;
/**
* The class PropertyChangeListListener.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeListListener<E> implements ListListener<E> {
private static final long serialVersionUID = 625853864429729560L;
private PropertyChangeSupport s;
private String propertyName;
public PropertyChangeListListener(PropertyChangeSupport s, String propertyName) {
this.s = s;
this.propertyName = propertyName;
}
public void add(int index, E newValue) {
s.fireIndexedPropertyChange(propertyName, index, null, newValue);
}
public void set(int index, E oldValue, E newValue) {
s.fireIndexedPropertyChange(propertyName, index, oldValue, newValue);
}
public void remove(int index, E oldValue) {
s.fireIndexedPropertyChange(propertyName, index, oldValue, null);
}
}

View file

@ -0,0 +1,126 @@
/**
* PropertyChangeMapListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.collections.ListenableCollections.MapListener;
/**
* The class PropertyChangeMapListener. This listener alway fires events with {@link Map} -> {@link Map} as the
* value parameters, as nonindexed collection properties. The events will be {@link MapEvent} instances.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeMapListener<K, V> implements MapListener<K, V> {
private static final long serialVersionUID = 625853864429729560L;
private PropertyChangeSupport s;
private Map<K, V> map;
private String propertyName;
public PropertyChangeMapListener(PropertyChangeSupport s, Map<K, V> map, String propertyName) {
this.s = s;
this.map = map;
this.propertyName = propertyName;
}
public void put(K key, V newValue) {
s.firePropertyChange(new MapPutEvent<K, V>(s.getSourceBean(), propertyName, map, key, newValue));
}
public void set(K key, V oldValue, V newValue) {
s.firePropertyChange(new MapSetEvent<K, V>(s.getSourceBean(), propertyName, map, key, oldValue, newValue));
}
public void remove(K key, V oldValue) {
s.firePropertyChange(new MapRemoveEvent<K, V>(s.getSourceBean(), propertyName, map, key, oldValue));
}
public static abstract class MapEvent<K, V> extends PropertyChangeEvent {
private static final long serialVersionUID = -651568020675693544L;
private Map<K, V> map;
private K key;
public MapEvent(Object source, String propertyName, Map<K, V> map, K key) {
super(source, propertyName, null, null);
this.map = map;
this.key = key;
}
@Override
public Map<K, V> getOldValue() {
//old and new value must not be equal
return null;
}
@Override
public Map<K, V> getNewValue() {
return map;
}
public K getKey() {
return key;
}
}
public static class MapPutEvent<K, V> extends MapEvent<K, V> {
private static final long serialVersionUID = 6006291474676939650L;
private V newElement;
public MapPutEvent(Object source, String propertyName, Map<K, V> map, K key, V newElement) {
super(source, propertyName, map, key);
this.newElement = newElement;
}
public V getNewElement() {
return newElement;
}
}
public static class MapSetEvent<K, V> extends MapEvent<K, V> {
private static final long serialVersionUID = -2419438379909500079L;
private V oldElement, newElement;
public MapSetEvent(Object source, String propertyName, Map<K, V> map, K key, V oldElement, V newElement) {
super(source, propertyName, map, key);
this.oldElement = oldElement;
this.newElement = newElement;
}
public V getOldElement() {
return oldElement;
}
public V getNewElement() {
return newElement;
}
}
public static class MapRemoveEvent<K, V> extends MapEvent<K, V> {
private static final long serialVersionUID = -2644879706878221895L;
private V oldElement;
public MapRemoveEvent(Object source, String propertyName, Map<K, V> map, K key, V oldElement) {
super(source, propertyName, map, key);
this.oldElement = oldElement;
}
public V getOldElement() {
return oldElement;
}
}
}

View file

@ -0,0 +1,96 @@
/**
* PropertyChangeSetListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import java.beans.PropertyChangeEvent;
import java.util.Set;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.collections.ListenableCollections.SetListener;
/**
* The class PropertyChangeSetListener. This listener always fires events with {@link Set} -> {@link Set} as the
* value parameters, as nonindexed collection properties. The events will be {@link SetEvent} instances.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeSetListener<E> implements SetListener<E> {
private static final long serialVersionUID = 625853864429729560L;
private PropertyChangeSupport s;
private Set<E> set;
private String propertyName;
public PropertyChangeSetListener(PropertyChangeSupport s, Set<E> set, String propertyName) {
this.s = s;
this.set = set;
this.propertyName = propertyName;
}
public void add(E newValue) {
s.firePropertyChange(new SetAddEvent<E>(s.getSourceBean(), propertyName, set, newValue));
}
public void remove(E oldValue) {
s.firePropertyChange(new SetRemoveEvent<E>(s.getSourceBean(), propertyName, set, oldValue));
}
public static abstract class SetEvent<E> extends PropertyChangeEvent {
private static final long serialVersionUID = -651568020675693544L;
private Set<E> set;
public SetEvent(Object source, String propertyName, Set<E> set) {
super(source, propertyName, null, null);
this.set = set;
}
@Override
public Set<E> getOldValue() {
//old and new value must not be equal
return null;
}
@Override
public Set<E> getNewValue() {
return set;
}
}
public static class SetAddEvent<E> extends SetEvent<E> {
private static final long serialVersionUID = 9041766866796759871L;
private E newElement;
public SetAddEvent(Object source, String propertyName, Set<E> set, E newElement) {
super(source, propertyName, set);
this.newElement = newElement;
}
public E getNewElement() {
return newElement;
}
}
public static class SetRemoveEvent<E> extends SetEvent<E> {
private static final long serialVersionUID = -1315342339926392385L;
private E oldElement;
public SetRemoveEvent(Object source, String propertyName, Set<E> set, E oldElement) {
super(source, propertyName, set);
this.oldElement = oldElement;
}
public E getOldElement() {
return oldElement;
}
}
}

View file

@ -0,0 +1,27 @@
/**
* AbstractLaternaBean.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl.lm;
import org.apache.log4j.Logger;
import org.mage.plugins.card.dl.beans.AbstractBoundBean;
import org.mage.plugins.card.dl.beans.EventListenerList;
import org.mage.plugins.card.dl.beans.properties.Properties;
import org.mage.plugins.card.dl.beans.properties.bound.BoundProperties;
/**
* The class AbstractLaternaBean.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class AbstractLaternaBean extends AbstractBoundBean {
protected static final Logger log = Logger.getLogger(AbstractLaternaBean.class);
protected Properties properties = new BoundProperties(s);
protected EventListenerList listeners = new EventListenerList();
}

View file

@ -0,0 +1,12 @@
package org.mage.plugins.card.dl.sources;
/**
*
* @author North
*/
public interface CardImageSource {
String generateURL(Integer collectorId, String cardSet) throws Exception;
String generateTokenUrl(String name, String set);
Float getAverageSize();
}

View file

@ -0,0 +1,78 @@
package org.mage.plugins.card.dl.sources;
import org.mage.plugins.card.dl.DownloadJob;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import static org.mage.plugins.card.dl.DownloadJob.fromURL;
import static org.mage.plugins.card.dl.DownloadJob.toFile;
public class GathererSets implements Iterable<DownloadJob> {
private final static String SETS_PATH = File.separator + "sets";
private final static File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + SETS_PATH);
private static File outDir = DEFAULT_OUT_DIR;
private static final String[] symbols = {"DIS", "DST", "GPT", "RAV", "MRD", "10E", "HOP", "SHM", "EVE", "APC", "TMP", "CHK"};
private static final String[] withMythics = {"ALA", "CFX", "ARB", "ZEN", "WWK", "ROE", "SOM", "M10", "M11", "M12", "DDF", "MBS", "NPH"};
private static final HashMap<String, String> symbolsReplacements = new HashMap<String, String>();
static {
symbolsReplacements.put("CFX", "CON");
symbolsReplacements.put("APC", "AP");
symbolsReplacements.put("TMP", "TE");
}
public GathererSets(String path) {
if (path == null) {
useDefaultDir();
} else {
changeOutDir(path);
}
}
@Override
public Iterator<DownloadJob> iterator() {
ArrayList<DownloadJob> jobs = new ArrayList<DownloadJob>();
for (String symbol : symbols) {
jobs.add(generateDownloadJob(symbol, "C"));
jobs.add(generateDownloadJob(symbol, "U"));
jobs.add(generateDownloadJob(symbol, "R"));
}
for (String symbol : withMythics) {
jobs.add(generateDownloadJob(symbol, "C"));
jobs.add(generateDownloadJob(symbol, "U"));
jobs.add(generateDownloadJob(symbol, "R"));
jobs.add(generateDownloadJob(symbol, "M"));
}
return jobs.iterator();
}
private DownloadJob generateDownloadJob(String set, String rarity) {
File dst = new File(outDir, set + "-" + rarity + ".jpg");
if (symbolsReplacements.containsKey(set)) {
set = symbolsReplacements.get(set);
}
String url = "http://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + rarity;
return new DownloadJob(set + "-" + rarity, fromURL(url), toFile(dst));
}
private void changeOutDir(String path) {
File file = new File(path + SETS_PATH);
if (file.exists()) {
outDir = file;
} else {
file.mkdirs();
if (file.exists()) {
outDir = file;
}
}
}
private void useDefaultDir() {
outDir = DEFAULT_OUT_DIR;
}
}

View file

@ -0,0 +1,109 @@
/**
* GathererSymbols.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl.sources;
import com.google.common.collect.AbstractIterator;
import org.mage.plugins.card.dl.DownloadJob;
import java.io.File;
import java.util.Iterator;
import static java.lang.String.format;
import static org.mage.plugins.card.dl.DownloadJob.fromURL;
import static org.mage.plugins.card.dl.DownloadJob.toFile;
/**
* The class GathererSymbols.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class GathererSymbols implements Iterable<DownloadJob> {
//TODO chaos and planeswalker symbol
//chaos: http://gatherer.wizards.com/Images/Symbols/chaos.gif
private final static String SYMBOLS_PATH = File.separator + "symbols";
private final static File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + SYMBOLS_PATH);
private static File outDir = DEFAULT_OUT_DIR;
private static final String urlFmt = "http://gatherer.wizards.com/handlers/image.ashx?size=%1$s&name=%2$s&type=symbol";
private static final String[] sizes = {"small", "medium", "large"};
private static final String[] symbols = {"W", "U", "B", "R", "G",
"W/U", "U/B", "B/R", "R/G", "G/W", "W/B", "U/R", "B/G", "R/W", "G/U",
"2/W", "2/U", "2/B", "2/R", "2/G",
"WP", "UP", "BP", "RP", "GP",
"X", "S", "T", "Q"};
private static final int minNumeric = 0, maxNumeric = 16;
public GathererSymbols(String path) {
if (path == null) {
useDefaultDir();
} else {
changeOutDir(path);
}
}
@Override
public Iterator<DownloadJob> iterator() {
return new AbstractIterator<DownloadJob>() {
private int sizeIndex, symIndex, numeric = minNumeric;
private File dir = new File(outDir, sizes[sizeIndex]);
@Override
protected DownloadJob computeNext() {
String sym;
if(symIndex < symbols.length) {
sym = symbols[symIndex++];
} else if(numeric <= maxNumeric) {
sym = "" + (numeric++);
} else {
sizeIndex++;
if(sizeIndex == sizes.length) return endOfData();
symIndex = 0;
numeric = 0;
dir = new File(outDir, sizes[sizeIndex]);
return computeNext();
}
String symbol = sym.replaceAll("/", "");
File dst = new File(dir, symbol + ".jpg");
if(symbol.equals("T")) symbol = "tap";
else if(symbol.equals("Q")) symbol = "untap";
else if(symbol.equals("S")) symbol = "snow";
String url = format(urlFmt, sizes[sizeIndex], symbol);
return new DownloadJob(sym, fromURL(url), toFile(dst));
}
};
}
private void changeOutDir(String path) {
File file = new File(path + SYMBOLS_PATH);
if (file.exists()) {
outDir = file;
} else {
file.mkdirs();
if (file.exists()) {
outDir = file;
}
}
}
private void useDefaultDir() {
outDir = DEFAULT_OUT_DIR;
}
}

View file

@ -0,0 +1,82 @@
package org.mage.plugins.card.dl.sources;
import java.util.HashMap;
import java.util.Map;
import org.mage.plugins.card.utils.CardImageUtils;
/**
*
* @author North
*/
public class MagicCardsImageSource implements CardImageSource {
private static CardImageSource instance = new MagicCardsImageSource();
private static final Map<String, String> setNameReplacement = new HashMap<String, String>() {
{
put("NPH", "new-phyrexia");
put("MBS", "mirrodin-besieged");
put("SOM", "scars-of-mirrodin");
put("M11", "magic-2011");
put("ROE", "rise-of-the-eldrazi");
put("PVC", "duel-decks-phyrexia-vs-the-coalition");
put("WWK", "worldwake");
put("ZEN", "zendikar");
put("HOP", "planechase");
put("M10", "magic-2010");
put("GVL", "duel-decks-garruk-vs-liliana");
put("ARB", "alara-reborn");
put("DVD", "duel-decks-divine-vs-demonic");
put("CON", "conflux");
put("JVC", "duel-decks-jace-vs-chandra");
put("ALA", "shards-of-alara");
put("EVE", "eventide");
put("SHM", "shadowmoor");
put("EVG", "duel-decks-elves-vs-goblins");
put("MOR", "morningtide");
put("LRW", "lorwyn");
put("10E", "tenth-edition");
put("CSP", "coldsnap");
put("CHK", "player-rewards-2004");
}
private static final long serialVersionUID = 1L;
};
public static CardImageSource getInstance() {
if (instance == null) {
instance = new MagicCardsImageSource();
}
return instance;
}
@Override
public String generateURL(Integer collectorId, String cardSet) throws Exception {
if (collectorId == null || cardSet == null) {
throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet);
}
String set = CardImageUtils.updateSet(cardSet, true);
String url = "http://magiccards.info/scans/en/";
url += set.toLowerCase() + "/" + collectorId + ".jpg";
return url;
}
@Override
public String generateTokenUrl(String name, String set) {
String _name = name.replaceAll(" ", "-").toLowerCase();
String _set = "not-supported-set";
if (setNameReplacement.containsKey(set)) {
_set = setNameReplacement.get(set);
} else {
_set += "-" + set;
}
String url = "http://magiccards.info/extras/token/" + _set + "/" + _name + ".jpg";
return url;
}
@Override
public Float getAverageSize() {
return 70.0f;
}
}

View file

@ -0,0 +1,82 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.mage.plugins.card.dl.sources;
import java.util.HashMap;
import java.util.Map;
import org.mage.plugins.card.utils.CardImageUtils;
/**
*
* @author North
*/
public class MtgatheringRuImageSource implements CardImageSource {
private static CardImageSource hqInstance;
private static CardImageSource mqInstance;
private static CardImageSource lqInstance;
private static final Map setsAliases;
static {
setsAliases = new HashMap();
setsAliases.put("M11", "magic2011");
}
private String quality;
public static CardImageSource getHqInstance() {
if (hqInstance == null) {
hqInstance = new MtgatheringRuImageSource("hq");
}
return hqInstance;
}
public static CardImageSource getMqInstance() {
if (mqInstance == null) {
mqInstance = new MtgatheringRuImageSource("md");
}
return mqInstance;
}
public static CardImageSource getLqInstance() {
if (lqInstance == null) {
lqInstance = new MtgatheringRuImageSource("lq");
}
return lqInstance;
}
public MtgatheringRuImageSource(String quality) {
this.quality = quality;
}
@Override
public String generateURL(Integer collectorId, String cardSet) throws Exception {
if (collectorId == null || cardSet == null) {
throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet);
}
if (setsAliases.get(cardSet) != null) {
String set = CardImageUtils.updateSet(cardSet, true);
String url = "http://mtgathering.ru/scans/en/";
url += set.toLowerCase() + "/" + quality + "/" + collectorId + ".jpg";
return url;
}
return null;
}
@Override
public String generateTokenUrl(String name, String set) {
return null;
}
@Override
public Float getAverageSize() {
if(quality.equalsIgnoreCase("hq"))
return 80.0f;
if(quality.equalsIgnoreCase("md"))
return 30.0f;
if(quality.equalsIgnoreCase("lq"))
return 9.0f;
return 0f;
}
}

View file

@ -0,0 +1,95 @@
package org.mage.plugins.card.dl.sources;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
/**
*
* @author North
*/
public class WizardCardsImageSource implements CardImageSource {
private static CardImageSource instance;
private static Map setsAliases;
private Map sets;
public static CardImageSource getInstance() {
if (instance == null) {
instance = new WizardCardsImageSource();
}
return instance;
}
public WizardCardsImageSource() {
sets = new HashMap();
setsAliases = new HashMap();
setsAliases.put("M12", "magic2012/cig");
setsAliases.put("NPH", "newphyrexia/spoiler");
setsAliases.put("MBS", "mirrodinbesieged/spoiler");
setsAliases.put("SOM", "scarsofmirrodin/spoiler");
setsAliases.put("M11", "magic2011/spoiler");
setsAliases.put("ROE", "riseoftheeldrazi/spoiler");
setsAliases.put("WWK", "worldwake/spoiler");
setsAliases.put("ZEN", "zendikar/spoiler");
setsAliases.put("M10", "magic2010/spoiler");
setsAliases.put("ARB", "alarareborn/spoiler");
setsAliases.put("CON", "conflux/spoiler");
setsAliases.put("ALA", "shardsofalara/spoiler");
}
private List<String> getSetLinks(String cardSet) {
List<String> setLinks = new ArrayList<String>();
try {
Document doc = Jsoup.connect("http://www.wizards.com/magic/tcg/article.aspx?x=mtg/tcg/" + (String) setsAliases.get(cardSet)).get();
Elements cardsImages = doc.select("img[height$=370]");
for (int i = 0; i < cardsImages.size(); i++) {
setLinks.add(cardsImages.get(i).attr("src"));
}
} catch (IOException ex) {
System.out.println("Exception when parsing the wizards page: " + ex.getMessage());
}
return setLinks;
}
@Override
public String generateURL(Integer collectorId, String cardSet) throws Exception {
if (collectorId == null || cardSet == null) {
throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet);
}
if (setsAliases.get(cardSet) != null) {
List<String> setLinks = (List<String>) sets.get(cardSet);
if (setLinks == null) {
setLinks = getSetLinks(cardSet);
sets.put(cardSet, setLinks);
}
String link;
if (setLinks.size() >= collectorId) {
link = setLinks.get(collectorId - 1);
} else {
link = setLinks.get(collectorId - 21);
link = link.replace(Integer.toString(collectorId - 20), (Integer.toString(collectorId - 20) + "a"));
}
if (!link.startsWith("http://")) {
link = "http://www.wizards.com" + link;
}
return link;
}
return null;
}
@Override
public String generateTokenUrl(String name, String set) {
return null;
}
@Override
public Float getAverageSize() {
return 60.0f;
}
}

View file

@ -0,0 +1,100 @@
package org.mage.plugins.card.images;
/**
*
* @author North
*/
public class CardInfo {
private String name;
private String set;
private Integer collectorId;
private boolean token;
public CardInfo(String name, String set, Integer collectorId) {
this.name = name;
this.set = set;
this.collectorId = collectorId;
token = false;
}
public CardInfo(String name, String set, Integer collectorId, boolean token) {
this.name = name;
this.set = set;
this.collectorId = collectorId;
this.token = token;
}
public CardInfo(final CardInfo card) {
this.name = card.name;
this.set = card.set;
this.collectorId = card.collectorId;
this.token = card.token;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final CardInfo other = (CardInfo) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if ((this.set == null) ? (other.set != null) : !this.set.equals(other.set)) {
return false;
}
if (this.collectorId != other.collectorId && (this.collectorId == null || !this.collectorId.equals(other.collectorId))) {
return false;
}
if (this.token != other.token) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 47 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 47 * hash + (this.set != null ? this.set.hashCode() : 0);
hash = 47 * hash + (this.collectorId != null ? this.collectorId.hashCode() : 0);
hash = 47 * hash + (this.token ? 1 : 0);
return hash;
}
public Integer getCollectorId() {
return collectorId;
}
public void setCollectorId(Integer collectorId) {
this.collectorId = collectorId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSet() {
return set;
}
public void setSet(String set) {
this.set = set;
}
public boolean isToken() {
return token;
}
public void setToken(boolean token) {
this.token = token;
}
}

View file

@ -0,0 +1,538 @@
package org.mage.plugins.card.images;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.management.ImmutableDescriptor;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import mage.cards.Card;
import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.dl.sources.CardImageSource;
import org.mage.plugins.card.dl.sources.MagicCardsImageSource;
import org.mage.plugins.card.dl.sources.MtgatheringRuImageSource;
import org.mage.plugins.card.dl.sources.WizardCardsImageSource;
import org.mage.plugins.card.properties.SettingsManager;
import org.mage.plugins.card.utils.CardImageUtils;
public class DownloadPictures extends DefaultBoundedRangeModel implements Runnable {
private int type;
private JTextField addr, port;
private JProgressBar bar;
private JOptionPane dlg;
private boolean cancel;
private JButton closeButton;
private JButton startDownloadButton;
private int cardIndex;
private ArrayList<CardInfo> cards;
private JComboBox jComboBox1;
private JLabel jLabel1;
private static boolean offlineMode = false;
private JCheckBox checkBox;
private final Object sync = new Object();
private String imagesPath;
private static CardImageSource cardImageSource;
private Proxy p;
private ExecutorService executor = Executors.newFixedThreadPool(10);
public static final Proxy.Type[] types = Proxy.Type.values();
public static void main(String[] args) {
startDownload(null, null, null);
}
public static void startDownload(JFrame frame, Set<Card> allCards, String imagesPath) {
ArrayList<CardInfo> cards = getNeededCards(allCards, imagesPath);
/*
* if (cards == null || cards.size() == 0) {
* JOptionPane.showMessageDialog(null,
* "All card pictures have been downloaded."); return; }
*/
DownloadPictures download = new DownloadPictures(cards, imagesPath);
JDialog dlg = download.getDlg(frame);
dlg.setVisible(true);
dlg.dispose();
download.setCancel(true);
}
public JDialog getDlg(JFrame frame) {
String title = "Downloading";
final JDialog dialog = this.dlg.createDialog(frame, title);
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
return dialog;
}
public void setCancel(boolean cancel) {
this.cancel = cancel;
}
public DownloadPictures(ArrayList<CardInfo> cards, String imagesPath) {
this.cards = cards;
this.imagesPath = imagesPath;
addr = new JTextField("Proxy Address");
port = new JTextField("Proxy Port");
bar = new JProgressBar(this);
JPanel p0 = new JPanel();
p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS));
// Proxy Choice
ButtonGroup bg = new ButtonGroup();
String[] labels = { "No Proxy", "HTTP Proxy", "SOCKS Proxy" };
for (int i = 0; i < types.length; i++) {
JRadioButton rb = new JRadioButton(labels[i]);
rb.addChangeListener(new ProxyHandler(i));
bg.add(rb);
p0.add(rb);
if (i == 0)
rb.setSelected(true);
}
// Proxy config
p0.add(addr);
p0.add(port);
p0.add(Box.createVerticalStrut(5));
jLabel1 = new JLabel();
jLabel1.setText("Please select server:");
jLabel1.setAlignmentX(Component.LEFT_ALIGNMENT);
p0.add(jLabel1);
p0.add(Box.createVerticalStrut(5));
ComboBoxModel jComboBox1Model = new DefaultComboBoxModel(new String[] { "magiccards.info", "wizards.com", "mtgathering.ru HQ", "mtgathering.ru MQ", "mtgathering.ru LQ"});
jComboBox1 = new JComboBox();
cardImageSource = MagicCardsImageSource.getInstance();
jComboBox1.setModel(jComboBox1Model);
jComboBox1.setAlignmentX(Component.LEFT_ALIGNMENT);
jComboBox1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox) e.getSource();
switch (cb.getSelectedIndex()) {
case 0:
cardImageSource = MagicCardsImageSource.getInstance();
break;
case 1:
cardImageSource = WizardCardsImageSource.getInstance();
break;
case 2:
cardImageSource = MtgatheringRuImageSource.getHqInstance();
break;
case 3:
cardImageSource = MtgatheringRuImageSource.getMqInstance();
break;
case 4:
cardImageSource = MtgatheringRuImageSource.getLqInstance();
break;
}
int count = DownloadPictures.this.cards.size();
float mb = (count * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format(cardIndex == count ? "%d of %d cards finished! Please close!"
: "%d of %d cards finished! Please wait! [%.1f Mb]", 0, count, mb));
}
});
p0.add(jComboBox1);
p0.add(Box.createVerticalStrut(5));
// Start
startDownloadButton = new JButton("Start download");
startDownloadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new Thread(DownloadPictures.this).start();
startDownloadButton.setEnabled(false);
checkBox.setEnabled(false);
}
});
p0.add(Box.createVerticalStrut(5));
// Progress
p0.add(bar);
bar.setStringPainted(true);
int count = cards.size();
float mb = (count * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format(cardIndex == cards.size() ? "%d of %d cards finished! Please close!"
: "%d of %d cards finished! Please wait! [%.1f Mb]", 0, cards.size(), mb));
Dimension d = bar.getPreferredSize();
d.width = 300;
bar.setPreferredSize(d);
p0.add(Box.createVerticalStrut(5));
checkBox = new JCheckBox("Download for current game only.");
p0.add(checkBox);
p0.add(Box.createVerticalStrut(5));
checkBox.setEnabled(!offlineMode);
checkBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int count = DownloadPictures.this.cards.size();
float mb = (count * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format(cardIndex == count ? "%d of %d cards finished! Please close!"
: "%d of %d cards finished! Please wait! [%.1f Mb]", 0, count, mb));
}
});
// JOptionPane
Object[] options = { startDownloadButton, closeButton = new JButton("Cancel") };
dlg = new JOptionPane(p0, JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[1]);
}
private static ArrayList<CardInfo> getNeededCards(Set<Card> allCards, String imagesPath) {
ArrayList<CardInfo> cardsToDownload = new ArrayList<CardInfo>();
/**
* read all card names and urls
*/
ArrayList<CardInfo> allCardsUrls = new ArrayList<CardInfo>();
try {
offlineMode = true;
for (Card card : allCards) {
if (card.getCardNumber() > 0 && !card.getExpansionSetCode().isEmpty()) {
CardInfo url = new CardInfo(card.getName(), card.getExpansionSetCode(), card.getCardNumber(), false);
allCardsUrls.add(url);
} else {
if (card.getCardNumber() < 1) {
System.err.println("There was a critical error!");
log.error("Card has no collector ID and won't be sent to client: " + card);
} else if (card.getExpansionSetCode().isEmpty()) {
System.err.println("There was a critical error!");
log.error("Card has no set name and won't be sent to client:" + card);
}
}
}
allCardsUrls.addAll(getTokenCardUrls());
} catch (Exception e) {
log.error(e);
}
File file;
/**
* check to see which cards we already have
*/
for (CardInfo card : allCardsUrls) {
boolean withCollectorId = false;
if (card.getName().equals("Forest") || card.getName().equals("Mountain") || card.getName().equals("Swamp") || card.getName().equals("Island")
|| card.getName().equals("Plains")) {
withCollectorId = true;
}
file = new File(CardImageUtils.getImagePath(card, withCollectorId, imagesPath));
if (!file.exists()) {
cardsToDownload.add(card);
}
}
for (CardInfo card : cardsToDownload) {
if (card.isToken()) {
log.info("Card to download: " + card.getName() + " (Token) ");
} else {
try {
log.info("Card to download: " + card.getName() + " (" + card.getSet() + ")");
} catch (Exception e) {
log.error(e);
}
}
}
return cardsToDownload;
}
private static ArrayList<CardInfo> getTokenCardUrls() throws RuntimeException {
ArrayList<CardInfo> list = new ArrayList<CardInfo>();
HashSet<String> filter = new HashSet<String>();
InputStream in = DownloadPictures.class.getClassLoader().getResourceAsStream("card-pictures-tok.txt");
readImageURLsFromFile(in, list, filter);
return list;
}
private static void readImageURLsFromFile(InputStream in, ArrayList<CardInfo> list, Set<String> filter) throws RuntimeException {
if (in == null) {
log.error("resources input stream is null");
return;
}
BufferedReader reader = null;
InputStreamReader input = null;
try {
input = new InputStreamReader(in);
reader = new BufferedReader(input);
String line;
line = reader.readLine();
while (line != null) {
line = line.trim();
if (line.startsWith("|")) { // new format
String[] params = line.split("\\|");
if (params.length >= 4) {
if (params[1].toLowerCase().equals("generate") && params[2].startsWith("TOK:")) {
String set = params[2].substring(4);
CardInfo card = new CardInfo(params[3], set, 0, true);
list.add(card);
}
} else {
log.error("wrong format for image urls: " + line);
}
}
line = reader.readLine();
}
} catch (Exception ex) {
log.error(ex);
throw new RuntimeException("DownloadPictures : readFile() error");
} finally {
if (input != null) {
try {
input.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private class ProxyHandler implements ChangeListener {
private int type;
public ProxyHandler(int type) {
this.type = type;
}
@Override
public void stateChanged(ChangeEvent e) {
if (((AbstractButton) e.getSource()).isSelected()) {
DownloadPictures.this.type = type;
addr.setEnabled(type != 0);
port.setEnabled(type != 0);
}
}
}
@Override
public void run() {
File base = new File(this.imagesPath != null ? imagesPath : Constants.IO.imageBaseDir);
if (!base.exists()) {
base.mkdir();
}
if (type == 0)
p = Proxy.NO_PROXY;
else
try {
p = new Proxy(types[type], new InetSocketAddress(addr.getText(), Integer.parseInt(port.getText())));
} catch (Exception ex) {
throw new RuntimeException("Gui_DownloadPictures : error 1 - " + ex);
}
if (p != null) {
HashSet<String> ignoreUrls = SettingsManager.getIntance().getIgnoreUrls();
update(0);
for (int i = 0; i < cards.size() && !cancel; i++) {
try {
CardInfo card = cards.get(i);
log.info("Downloading card: " + card.getName() + " (" + card.getSet() + ")");
String url;
if (ignoreUrls.contains(card.getSet()) || card.isToken()) {
if (card.getCollectorId() != 0) {
continue;
}
url = cardImageSource.generateTokenUrl(card.getName(), card.getSet());
} else {
url = cardImageSource.generateURL(card.getCollectorId(), card.getSet());
}
if (url != null) {
Runnable task = new DownloadTask(card, new URL(url), imagesPath);
executor.execute(task);
} else {
synchronized (sync) {
update(cardIndex + 1);
}
}
} catch (Exception ex) {
log.error(ex, ex);
}
}
executor.shutdown();
while (!executor.isTerminated()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {}
}
}
closeButton.setText("Close");
}
private final class DownloadTask implements Runnable {
private CardInfo card;
private URL url;
private String imagesPath;
public DownloadTask(CardInfo card, URL url, String imagesPath) {
this.card = card;
this.url = url;
this.imagesPath = imagesPath;
}
@Override
public void run() {
try {
BufferedInputStream in = new BufferedInputStream(url.openConnection(p).getInputStream());
createDirForCard(card, imagesPath);
boolean withCollectorId = false;
if (card.getName().equals("Forest") || card.getName().equals("Mountain") || card.getName().equals("Swamp")
|| card.getName().equals("Island") || card.getName().equals("Plains")) {
withCollectorId = true;
}
File fileOut = new File(CardImageUtils.getImagePath(card, withCollectorId));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileOut));
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
// user cancelled
if (cancel) {
in.close();
out.flush();
out.close();
// delete what was written so far
fileOut.delete();
}
out.write(buf, 0, len);
}
in.close();
out.flush();
out.close();
synchronized (sync) {
update(cardIndex + 1);
}
} catch (Exception e) {
log.error(e, e);
}
}
}
private static File createDirForCard(CardInfo card, String imagesPath) throws Exception {
File setDir = new File(CardImageUtils.getImageDir(card, imagesPath));
if (!setDir.exists()) {
setDir.mkdirs();
}
return setDir;
}
private void update(int card) {
this.cardIndex = card;
int count = DownloadPictures.this.cards.size();
if (cardIndex < count) {
float mb = ((count - card) * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format("%d of %d cards finished! Please wait! [%.1f Mb]",
card, count, mb));
} else {
Iterator<CardInfo> cardsIterator = DownloadPictures.this.cards.iterator();
while (cardsIterator.hasNext()) {
CardInfo cardInfo = cardsIterator.next();
boolean withCollectorId = false;
if (cardInfo.getName().equals("Forest") || cardInfo.getName().equals("Mountain") || cardInfo.getName().equals("Swamp") || cardInfo.getName().equals("Island")
|| cardInfo.getName().equals("Plains")) {
withCollectorId = true;
}
File file = new File(CardImageUtils.getImagePath(cardInfo, withCollectorId));
if (file.exists()) {
cardsIterator.remove();
}
}
count = DownloadPictures.this.cards.size();
if (count == 0) {
bar.setString(String.format("0 cards remaining! Please close!", count));
} else {
bar.setString(String.format("%d cards remaining! Please choose another source!", count));
executor = Executors.newFixedThreadPool(10);
startDownloadButton.setEnabled(true);
}
}
}
private static final Logger log = Logger.getLogger(DownloadPictures.class);
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,253 @@
package org.mage.plugins.card.images;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import mage.view.CardView;
import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.utils.CardImageUtils;
import com.google.common.base.Function;
import com.google.common.collect.ComputationException;
import com.google.common.collect.MapMaker;
import com.mortennobel.imagescaling.ResampleOp;
import java.awt.Graphics2D;
/**
* This class stores ALL card images in a cache with soft values. this means
* that the images may be collected when they are not needed any more, but will
* be kept as long as possible.
*
* Key format: "<cardname>#<setname>#<collectorID>#<param>"
*
* where param is:
*
* <ul>
* <li>#Normal: request for unrotated image</li>
* <li>#Tapped: request for rotated image</li>
* <li>#Cropped: request for cropped image that is used for Shandalar like card
* look</li>
* </ul>
*/
public class ImageCache {
private static final Logger log = Logger.getLogger(ImageCache.class);
private static final Map<String, BufferedImage> imageCache;
/**
* Common pattern for keys.
* Format: "<cardname>#<setname>#<collectorID>"
*/
private static final Pattern KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)");
static {
imageCache = new MapMaker().softValues().makeComputingMap(new Function<String, BufferedImage>() {
@Override
public BufferedImage apply(String key) {
try {
boolean thumbnail = false;
if (key.endsWith("#thumb")) {
thumbnail = true;
key = key.replace("#thumb", "");
}
Matcher m = KEY_PATTERN.matcher(key);
if (m.matches()) {
String name = m.group(1);
String set = m.group(2);
Integer collectorId = Integer.parseInt(m.group(3));
CardInfo info = new CardInfo(name, set, collectorId);
if (collectorId == 0) info.setToken(true);
String path = CardImageUtils.getImagePath(info);
if (path == null) return null;
File file = new File(path);
if (thumbnail && path.endsWith(".jpg")) {
String thumbnailPath = path.replace(".jpg", ".thumb.jpg");
File thumbnailFile = new File(thumbnailPath);
if (thumbnailFile.exists()) {
//log.debug("loading thumbnail for " + key + ", path="+thumbnailPath);
return loadImage(thumbnailFile);
} else {
BufferedImage image = loadImage(file);
image = getWizardsCard(image);
if (image == null) return null;
//log.debug("creating thumbnail for " + key);
return makeThumbnail(image, thumbnailPath);
}
} else {
return getWizardsCard(loadImage(file));
}
} else {
throw new RuntimeException(
"Requested image doesn't fit the requirement for key (<cardname>#<setname>#<collectorID>): " + key);
}
} catch (Exception ex) {
if (ex instanceof ComputationException)
throw (ComputationException) ex;
else
throw new ComputationException(ex);
}
}
});
}
public static BufferedImage getWizardsCard(BufferedImage image) {
if (image.getWidth() == 265 && image.getHeight() == 370) {
BufferedImage crop = new BufferedImage(256, 360, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = crop.createGraphics();
graphics2D.drawImage(image, 0, 0, 255, 360, 5, 5, 261, 365, null);
graphics2D.dispose();
return crop;
} else {
return image;
}
}
public static BufferedImage getThumbnail(CardView card) {
String key = getKey(card) + "#thumb";
//log.debug("#key: " + key);
return getImage(key);
}
public static BufferedImage getImageOriginal(CardView card) {
String key = getKey(card);
//log.debug("#key: " + key);
return getImage(key);
}
/**
* Returns the Image corresponding to the key
*/
private static BufferedImage getImage(String key) {
try {
BufferedImage image = imageCache.get(key);
return image;
} catch (NullPointerException ex) {
// unfortunately NullOutputException, thrown when apply() returns
// null, is not public
// NullOutputException is a subclass of NullPointerException
// legitimate, happens when a card has no image
return null;
} catch (ComputationException ex) {
if (ex.getCause() instanceof NullPointerException)
return null;
log.error(ex,ex);
return null;
}
}
/**
* Returns the map key for a card, without any suffixes for the image size.
*/
private static String getKey(CardView card) {
String set = card.getExpansionSetCode();
String key = card.getName() + "#" + set + "#" + String.valueOf(card.getCardNumber());
return key;
}
/**
* Load image from file
*
* @param file
* file to load image from
* @return {@link BufferedImage}
*/
public static BufferedImage loadImage(File file) {
BufferedImage image = null;
if (!file.exists()) {
return null;
}
try {
image = ImageIO.read(file);
} catch (Exception e) {
log.error(e, e);
}
return image;
}
public static BufferedImage makeThumbnail(BufferedImage original, String path) {
BufferedImage image = getResizedImage(original, Constants.THUMBNAIL_SIZE_FULL);
File imagePath = new File(path);
try {
//log.debug("thumbnail path:"+path);
ImageIO.write(image, "jpg", imagePath);
} catch (Exception e) {
log.error(e,e);
}
return image;
}
/**
* Returns an image scaled to the size given
*/
public static BufferedImage getNormalSizeImage(BufferedImage original) {
if (original == null) {
return null;
}
int srcWidth = original.getWidth();
int srcHeight = original.getHeight();
int tgtWidth = Constants.CARD_SIZE_FULL.width;
int tgtHeight = Constants.CARD_SIZE_FULL.height;
if (srcWidth == tgtWidth && srcHeight == tgtHeight)
return original;
ResampleOp resampleOp = new ResampleOp(tgtWidth, tgtHeight);
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns an image scaled to the size appropriate for the card picture
* panel For future use.
*/
private static BufferedImage getFullSizeImage(BufferedImage original, double scale) {
if (scale == 1)
return original;
ResampleOp resampleOp = new ResampleOp((int) (original.getWidth() * scale), (int) (original.getHeight() * scale));
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns an image scaled to the size appropriate for the card picture
* panel
*/
public static BufferedImage getResizedImage(BufferedImage original, Rectangle sizeNeed) {
ResampleOp resampleOp = new ResampleOp(sizeNeed.width, sizeNeed.height);
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns the image appropriate to display the card in the picture panel
*/
public static BufferedImage getImage(CardView card, int width, int height) {
String key = getKey(card);
BufferedImage original = getImage(key);
if (original == null)
return null;
double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight());
if (scale > 1)
scale = 1;
return getFullSizeImage(original, scale);
}
}

View file

@ -0,0 +1,207 @@
package org.mage.plugins.card.info;
import mage.Constants;
import mage.components.CardInfoPane;
import mage.utils.CardUtil;
import mage.utils.ThreadUtils;
import mage.view.CardView;
import mage.view.CounterView;
import mage.view.PermanentView;
import org.mage.card.arcane.ManaSymbols;
import org.mage.card.arcane.UI;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* Card info pane for displaying card rules.
* Supports drawing mana symbols.
*
* @author nantuko
*/
public class CardInfoPaneImpl extends JEditorPane implements CardInfoPane {
private CardView currentCard;
public CardInfoPaneImpl() {
UI.setHTMLEditorKit(this);
setEditable(false);
setBackground(Color.white);
}
public void setCard(final CardView card) {
if (card == null) return;
if (isCurrentCard(card)) return;
currentCard = card;
ThreadUtils.threadPool.submit(new Runnable() {
public void run() {
try {
if (!card.equals(currentCard)) return;
String manaCost = "";
for (String m : card.getManaCost()) {
manaCost += m;
}
String castingCost = UI.getDisplayManaCost(manaCost);
castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, false);
int symbolCount = 0;
int offset = 0;
while ((offset = castingCost.indexOf("<img", offset) + 1) != 0)
symbolCount++;
List<String> rules = card.getRules();
List<String> rulings = new ArrayList<String>(rules);
if (card instanceof PermanentView) {
List<CounterView> counters = ((PermanentView) card).getCounters();
int count = counters != null ? counters.size() : 0;
if (count > 0) {
StringBuilder sb = new StringBuilder();
int index = 0;
for (CounterView counter : ((PermanentView) card).getCounters()) {
if (counter.getCount() > 0) {
if (index == 0) {
sb.append("<b>Counters:</b> ");
} else {
sb.append(", ");
}
sb.append(counter.getCount() + "x<i>" + counter.getName() + "</i>");
index++;
}
}
rulings.add(sb.toString());
}
int damage = ((PermanentView)card).getDamage();
if (damage > 0) {
rulings.add("<span color='red'><b>Damage dealt:</b> " + damage + "</span>");
}
}
boolean smallImages = true;
int fontSize = 11;
String fontFamily = "tahoma";
/*if (prefs.fontFamily == CardFontFamily.arial)
fontFamily = "arial";
else if (prefs.fontFamily == CardFontFamily.verdana) {
fontFamily = "verdana";
}*/
final StringBuffer buffer = new StringBuffer(512);
buffer.append("<html><body style='font-family:");
buffer.append(fontFamily);
buffer.append(";font-size:");
buffer.append(fontSize);
buffer.append("pt;margin:0px 1px 0px 1px'>");
buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%'>");
buffer.append("<tr><td valign='top'><b>");
buffer.append(card.getName());
buffer.append("</b></td><td align='right' valign='top' style='width:");
buffer.append(symbolCount * 11 + 1);
buffer.append("px'>");
buffer.append(castingCost);
buffer.append("</td></tr></table>");
buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%'><tr><td style='margin-left: 1px'>");
buffer.append(getTypes(card));
buffer.append("</td><td align='right'>");
switch (card.getRarity()) {
case RARE:
buffer.append("<b color='#FFBF00'>");
break;
case UNCOMMON:
buffer.append("<b color='silver'>");
break;
case COMMON:
buffer.append("<b color='black'>");
break;
case MYTHIC:
buffer.append("<b color='#D5330B'>");
break;
}
String rarity = card.getRarity().getCode();
if (card.getExpansionSetCode() != null) {
buffer.append(ManaSymbols.replaceSetCodeWithHTML(card.getExpansionSetCode().toUpperCase(), rarity));
}
buffer.append("</td></tr></table>");
String pt = "";
if (CardUtil.isCreature(card)) {
pt = card.getPower() + "/" + card.getToughness();
} else if (CardUtil.isPlaneswalker(card)) {
pt = card.getLoyalty().toString();
}
if (pt.length() > 0) {
buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%' valign='bottom'><tr><td>");
buffer.append("<b>");
buffer.append(pt);
buffer.append("</b>");
buffer.append("</td></tr></table>");
}
String legal = "";
if (rulings.size() > 0) {
legal = legal.replaceAll("#([^#]+)#", "<i>$1</i>");
legal = legal.replaceAll("\\s*//\\s*", "<hr width='50%'>");
legal = legal.replace("\r\n", "<div style='font-size:5pt'></div>");
legal += "<br>";
for (String ruling : rulings) {
if (!ruling.replace(".", "").trim().isEmpty()) {
legal += "<p style='margin: 2px'>";
legal += ruling;
legal += "</p>";
}
}
}
if (legal.length() > 0) {
//buffer.append("<br>");
legal = legal.replaceAll("\\{this\\}", card.getName());
legal = legal.replaceAll("\\{source\\}", card.getName());
buffer.append(ManaSymbols.replaceSymbolsWithHTML(legal, smallImages));
}
buffer.append("<br></body></html>");
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (!card.equals(currentCard)) return;
setText(buffer.toString());
//System.out.println(buffer.toString());
setCaretPosition(0);
//ThreadUtils.sleep(300);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private String getTypes(CardView card) {
String types = "";
for (String superType : card.getSuperTypes()) {
types += superType + " ";
}
for (Constants.CardType cardType : card.getCardTypes()) {
types += cardType.toString() + " ";
}
if (card.getSubTypes().size() > 0) {
types += "- ";
}
for (String subType : card.getSubTypes()) {
types += subType + " ";
}
return types.trim();
}
public boolean isCurrentCard(CardView card) {
return currentCard != null && card.equals(currentCard);
}
}

View file

@ -0,0 +1,78 @@
package org.mage.plugins.card.properties;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import org.mage.plugins.card.constants.Constants;
public class SettingsManager {
private static SettingsManager settingsManager = null;
public static SettingsManager getIntance() {
if (settingsManager == null) {
synchronized (SettingsManager.class) {
if (settingsManager == null) settingsManager = new SettingsManager();
}
}
return settingsManager;
}
private SettingsManager() {
loadImageProperties();
}
public void reloadImageProperties() {
loadImageProperties();
}
private void loadImageProperties() {
imageUrlProperties = new Properties();
try {
InputStream is = SettingsManager.class.getClassLoader().getResourceAsStream(Constants.IO.IMAGE_PROPERTIES_FILE);
if (is == null)
throw new RuntimeException("Couldn't load " + Constants.IO.IMAGE_PROPERTIES_FILE);
imageUrlProperties.load(is);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public String getSetNameReplacement(String setName) {
String result = setName;
if (imageUrlProperties != null) {
result = imageUrlProperties.getProperty(setName, setName);
}
return result;
}
public HashSet<String> getIgnoreUrls() {
HashSet<String> ignoreUrls = new HashSet<String>();
if (imageUrlProperties != null) {
String result = imageUrlProperties.getProperty("ignore.urls");
if (result != null) {
String[] ignore = result.split(",");
ignoreUrls.addAll(Arrays.asList(ignore));
}
}
return ignoreUrls;
}
public ArrayList<String> getTokenLookupOrder() {
ArrayList<String> order = new ArrayList<String>();
if (imageUrlProperties != null) {
String result = imageUrlProperties.getProperty("token.lookup.order");
if (result != null) {
String[] sets = result.split(",");
order.addAll(Arrays.asList(sets));
}
}
return order;
}
private Properties imageUrlProperties;
}

View file

@ -0,0 +1,71 @@
package org.mage.plugins.card.utils;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
/**
* Utility class for creating BufferedImage object from Image instance.
*
* @author nantuko
*/
public class BufferedImageBuilder {
private static final int DEFAULT_IMAGE_TYPE = BufferedImage.TYPE_INT_RGB;
/**
* Hide constructor
*/
private BufferedImageBuilder() {
}
public static BufferedImage bufferImage(Image image) {
return bufferImage(image, DEFAULT_IMAGE_TYPE);
}
public static BufferedImage bufferImage(Image image, int type) {
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
Graphics2D g = bufferedImage.createGraphics();
g.drawImage(image, null, null);
//waitForImage(bufferedImage);
return bufferedImage;
}
private void waitForImage(BufferedImage bufferedImage) {
final ImageLoadStatus imageLoadStatus = new ImageLoadStatus();
bufferedImage.getHeight(new ImageObserver() {
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
if (infoflags == ALLBITS) {
imageLoadStatus.heightDone = true;
return true;
}
return false;
}
});
bufferedImage.getWidth(new ImageObserver() {
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
if (infoflags == ALLBITS) {
imageLoadStatus.widthDone = true;
return true;
}
return false;
}
});
while (!imageLoadStatus.widthDone && !imageLoadStatus.heightDone) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
}
}
}
class ImageLoadStatus {
public boolean widthDone = false;
public boolean heightDone = false;
}
}

View file

@ -0,0 +1,154 @@
package org.mage.plugins.card.utils;
import java.io.File;
import java.util.HashMap;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.images.CardInfo;
import org.mage.plugins.card.properties.SettingsManager;
public class CardImageUtils {
private static HashMap<CardInfo, String> pathCache = new HashMap<CardInfo, String>();
/**
* Get path to image for specific card.
*
* @param card
* card to get path for
* @return String if image exists, else null
*/
public static String getImagePath(CardInfo card) {
String filePath;
String suffix = ".jpg";
File file = null;
if (card.isToken()) {
if (pathCache.containsKey(card)) {
return pathCache.get(card);
}
filePath = getTokenImagePath(card);
file = new File(filePath);
if (!file.exists()) {
filePath = searchForCardImage(card);
file = new File(filePath);
}
if (file.exists()) {
pathCache.put(card, filePath);
}
} else {
filePath = getImagePath(card, false);
file = new File(filePath);
if (!file.exists()) {
filePath = getImagePath(card, true);
file = new File(filePath);
}
}
/**
* try current directory
*/
if (file == null || !file.exists()) {
filePath = cleanString(card.getName()) + suffix;
file = new File(filePath);
}
if (file.exists()) {
return filePath;
} else {
return null;
}
}
private static String getTokenImagePath(CardInfo card) {
String filename = getImagePath(card, false);
File file = new File(filename);
if (!file.exists()) {
CardInfo updated = new CardInfo(card);
updated.setName(card.getName() + " 1");
filename = getImagePath(updated, false);
file = new File(filename);
if (!file.exists()) {
updated = new CardInfo(card);
updated.setName(card.getName() + " 2");
filename = getImagePath(updated, false);
file = new File(filename);
}
}
return filename;
}
private static String searchForCardImage(CardInfo card) {
File file = null;
String path = "";
CardInfo c = new CardInfo(card);
for (String set : SettingsManager.getIntance().getTokenLookupOrder()) {
c.setSet(set);
path = getTokenImagePath(c);
file = new File(path);
if (file.exists()) {
pathCache.put(card, path);
return path;
}
}
return "";
}
public static String cleanString(String in) {
in = in.trim();
StringBuilder out = new StringBuilder();
char c;
for (int i = 0; i < in.length(); i++) {
c = in.charAt(i);
if (c == ' ' || c == '-')
out.append('_');
else if (Character.isLetterOrDigit(c)) {
out.append(c);
}
}
return out.toString().toLowerCase();
}
public static String updateSet(String cardSet, boolean forUrl) {
String set = cardSet.toLowerCase();
if (set.equals("con")) {
set = "cfx";
}
if (forUrl) {
set = SettingsManager.getIntance().getSetNameReplacement(set);
}
return set;
}
public static String getImageDir(CardInfo card, String imagesPath) {
if (card.getSet() == null) {
return "";
}
String set = updateSet(card.getSet(), false).toUpperCase();
String imagesDir = (imagesPath != null ? imagesPath : Constants.IO.imageBaseDir);
if (card.isToken()) {
return imagesDir + File.separator + "TOK" + File.separator + set;
} else {
return imagesDir + File.separator + set;
}
}
public static String getImagePath(CardInfo card, boolean withCollector) {
return getImagePath(card, withCollector, null);
}
public static String getImagePath(CardInfo card, boolean withCollector, String imagesPath) {
if (withCollector) {
return getImageDir(card, imagesPath) + File.separator + card.getName() + "." + card.getCollectorId() + ".full.jpg";
} else {
return getImageDir(card, imagesPath) + File.separator + card.getName() + ".full.jpg";
}
}
}

View file

@ -0,0 +1,7 @@
package org.mage.plugins.card.utils;
import java.awt.*;
public interface ImageManager {
public Image getSicknessImage();
}

View file

@ -0,0 +1,52 @@
package org.mage.plugins.card.utils;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
public class Transparency {
public static Image makeColorTransparent(Image im, final Color color) {
ImageFilter filter = new RGBImageFilter() {
// the color we are looking for... Alpha bits are set to opaque
public int markerRGB = color.getRGB() | 0xFF000000;
@Override
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0xFF000000) == markerRGB) {
// Mark the alpha bits as zero - transparent
return 0x00FFFFFF & rgb;
} else {
// nothing to do
return rgb;
}
}
};
ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
public static BufferedImage makeImageTranslucent(BufferedImage source,
double alpha) {
BufferedImage target = new BufferedImage(source.getWidth(), source
.getHeight(), java.awt.Transparency.TRANSLUCENT);
// Get the images graphics
Graphics2D g = target.createGraphics();
// Set the Graphics composite to Alpha
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
(float) alpha));
// Draw the image into the prepared reciver image
g.drawImage(source, null, 0, 0);
// let go of all system resources in this Graphics
g.dispose();
// Return the image
return target;
}
}

View file

@ -0,0 +1,57 @@
package org.mage.plugins.card.utils.impl;
import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.net.URL;
import javax.imageio.ImageIO;
import org.mage.plugins.card.utils.BufferedImageBuilder;
import org.mage.plugins.card.utils.ImageManager;
import org.mage.plugins.card.utils.Transparency;
public class ImageManagerImpl implements ImageManager {
private static ImageManagerImpl fInstance = new ImageManagerImpl();
public static ImageManagerImpl getInstance() {
return fInstance;
}
@Override
public BufferedImage getSicknessImage() {
if (imageSickness == null) {
Image image = getImageFromResourceTransparent("/sickness.png", Color.WHITE, new Rectangle(296, 265));
Toolkit tk = Toolkit.getDefaultToolkit();
image = tk.createImage(new FilteredImageSource(image.getSource(), new CropImageFilter(0, 0, 200, 285)));
imageSickness = BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB);
}
return imageSickness;
}
protected static Image getImageFromResourceTransparent(String path, Color mask, Rectangle rec) {
BufferedImage image = null;
Image imageCardTransparent = null;
Image resized = null;
URL imageURL = ImageManager.class.getResource(path);
try {
image = ImageIO.read(imageURL);
imageCardTransparent = Transparency.makeColorTransparent(image, mask);
resized = imageCardTransparent.getScaledInstance(rec.width, rec.height, java.awt.Image.SCALE_SMOOTH);
} catch (Exception e) {
e.printStackTrace();
}
return resized;
}
private static BufferedImage imageSickness = null;
}