/**
* This file is part of JSkat.
*
* JSkat is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JSkat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JSkat. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jskat.gui.swing;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import javax.swing.JPanel;
import org.jskat.data.JSkatOptions;
import org.jskat.gui.img.JSkatGraphicRepository;
import org.jskat.util.Card;
import org.jskat.util.CardList;
import org.jskat.util.GameType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Panel for showing cards.
*/
public class CardPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory.getLogger(CardPanel.class);
protected final JSkatGraphicRepository bitmaps;
protected Double scaleFactor = 1.0;
private Boolean showBackside = true;
private Integer mouseXPosition = Integer.MAX_VALUE;
protected Integer activeCardMinXPosition = Integer.MAX_VALUE;
protected Integer activeCardMaxXPosition = Integer.MAX_VALUE;
/**
* Holds the game type for the sorting order.
*/
private GameType sortGameType = GameType.GRAND;
protected CardList cards;
/**
* Creates a new instance of CardPanel.
*
* @param scaleFactor
* Scale factor for cards
* @param showBackside
* TRUE if the Card should hide its face
*/
public CardPanel(final Double scaleFactor, final Boolean showBackside) {
setLayout(LayoutFactory.getMigLayout("fill", "fill", "fill")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
createMouseAdapter();
this.bitmaps = JSkatGraphicRepository.INSTANCE;
this.scaleFactor = scaleFactor;
this.showBackside = showBackside;
this.cards = new CardList();
setOpaque(false);
}
private void createMouseAdapter() {
MouseAdapter adapter = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
CardPanel.this.mouseXPosition = e.getX();
repaintIfNecessary();
}
@Override
public void mouseEntered(MouseEvent e) {
CardPanel.this.mouseXPosition = e.getX();
resetActiveCardPosition();
repaintIfNecessary();
}
@Override
public void mouseExited(MouseEvent e) {
resetMousePositions();
repaint();
}
};
addMouseMotionListener(adapter);
addMouseListener(adapter);
}
private void resetMousePositions() {
this.mouseXPosition = Integer.MAX_VALUE;
resetActiveCardPosition();
}
private void resetActiveCardPosition() {
this.activeCardMinXPosition = Integer.MAX_VALUE;
this.activeCardMaxXPosition = Integer.MAX_VALUE;
}
protected void repaintIfNecessary() {
if (!this.showBackside
&& (this.mouseXPosition < this.activeCardMinXPosition || this.mouseXPosition > this.activeCardMaxXPosition)) {
repaint();
}
}
/**
* Adds a card.
*
* @param newCard
* Card
*/
public final void addCard(final Card newCard) {
this.cards.add(newCard);
this.cards.sort(this.sortGameType);
repaint();
}
/**
* Adds a list of cards.
*
* @param newCards
* List of cards
*/
public final void addCards(final CardList newCards) {
this.cards.addAll(newCards);
this.cards.sort(this.sortGameType);
repaint();
}
/**
* Removes a card.
*
* @param cardToRemove
* Card to remove
*/
public final void removeCard(final Card cardToRemove) {
if (cards.contains(cardToRemove)) {
cards.remove(cardToRemove);
} else if (cards.size() > 0) {
// card panels with hidden cards may contain unknown cards
// remove the last one
cards.remove(cards.size() - 1);
}
resetActiveCardPosition();
repaint();
}
/**
* Gets a card.
*
* @param index
* Index of card
* @return Card
*/
public final Card get(final int index) {
return this.cards.get(index);
}
/**
* @see JPanel#paintComponent(Graphics)
*/
@Override
protected final synchronized void paintComponent(final Graphics g) {
super.paintComponent(g);
// copying cards prevents ConcurrentModificationException
final CardList cardsToPaint = new CardList(this.cards);
// rendering hints
final Graphics2D g2D = (Graphics2D) g;
g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// calculate card gap
int panelWidth = getWidth();
int cardWidth = this.bitmaps.getCardImage(Card.CJ).getWidth(this);
int cardGap = calculateCardGap(panelWidth, cardWidth);
adjustActiveCardPositions(cardWidth, cardGap);
paintAllCards(cardsToPaint, g2D, cardWidth, cardGap);
// drawMouseMarkers(g2D);
}
private int calculateCardGap(final int panelWidth, final int cardWidth) {
int cardGap = cardWidth;
if (this.cards.size() * cardGap > panelWidth) {
// cards overlap
cardGap = (panelWidth - cardWidth) / (this.cards.size() - 1);
}
return cardGap;
}
private void paintAllCards(final CardList cardsToPaint,
final Graphics2D g2D, final int cardWidth, int cardGap) {
int cardNo = 0;
for (final Card card : cardsToPaint) {
final AffineTransform transform = new AffineTransform();
transform.scale(this.scaleFactor, this.scaleFactor);
if (cardNo * cardGap <= this.activeCardMinXPosition) {
transform.translate(cardNo * cardGap, 0);
} else if (this.activeCardMaxXPosition < cardNo * cardGap + cardWidth) {
transform.translate((cardNo - 1) * cardGap + cardWidth, 0);
}
g2D.drawImage(getCardImage(card), transform, this);
cardNo++;
}
}
private void adjustActiveCardPositions(int cardWidth, int cardGap) {
if (this.mouseXPosition < this.activeCardMinXPosition) {
this.activeCardMinXPosition = (this.mouseXPosition / cardGap) * cardGap;
} else if (this.mouseXPosition > this.activeCardMaxXPosition) {
this.activeCardMinXPosition = ((this.mouseXPosition - cardWidth + cardGap) / (cardGap))
* cardGap;
}
this.activeCardMaxXPosition = this.activeCardMinXPosition + cardWidth;
}
private Image getCardImage(final Card card) {
Image image = null;
if (this.showBackside) {
image = this.bitmaps.getCardImage(null);
} else {
if (card == null) {
// e.g. in debug mode
image = this.bitmaps.getCardImage(null);
} else {
image = this.bitmaps.getCardImage(card);
}
}
return image;
}
/**
* Clears the card panel.
*/
public final void clearCards() {
this.cards.clear();
repaint();
}
/**
* Flips the cards.
*/
public final void flipCards() {
if (this.showBackside) {
showCards();
} else {
hideCards();
}
}
/**
* Shows the cards.
*/
public final void showCards() {
this.showBackside = false;
repaint();
}
/**
* Hides the cards.
*/
public final void hideCards() {
if (!JSkatOptions.instance().isCheatDebugMode().booleanValue()) {
this.showBackside = true;
repaint();
}
}
/**
* Returns the number of cards.
*
* @return Number of cards
*/
public final int getCardCount() {
return this.cards.size();
}
/**
* Sets the sorting order.
*
* @param newGameType
* Game type
*/
public final void setSortType(final GameType newGameType) {
this.sortGameType = newGameType;
this.cards.sort(this.sortGameType);
repaint();
}
/**
* Returns the cards.
*
* @return Cards
*/
public final CardList getCards() {
return this.cards.getImmutableCopy();
}
}