/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.client.ui.tokenpanel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Transparency;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.CellConstraints.Alignment;
import com.jgoodies.forms.layout.FormLayout;
import com.t3.client.AppPreferences;
import com.t3.client.TabletopTool;
import com.t3.client.ui.token.BarTokenOverlay;
import com.t3.client.ui.token.BooleanTokenOverlay;
import com.t3.image.ImageUtil;
import com.t3.model.initiative.InitiativeList.TokenInitiative;
import com.t3.model.Token;
import com.t3.swing.ImageLabel;
import com.t3.swing.SwingUtil;
import com.t3.util.GraphicsUtil;
import com.t3.util.ImageManager;
/**
* This is the renderer that shows a token in the initiative panel.
*
* @author Jay
*/
public class InitiativeListCellRenderer extends JPanel implements ListCellRenderer {
/*---------------------------------------------------------------------------------------------
* Instance Variables
*-------------------------------------------------------------------------------------------*/
/**
* The label used to display the current item indicator.
*/
private final JLabel currentIndicator;
/**
* The label used to display the item's name and icon.
*/
private final JLabel name;
/**
* This is the panel showing initiative. It contains the state for display.
*/
private final InitiativePanel panel;
/**
* Used to draw the background of the item.
*/
private ImageLabel backgroundImageLabel;
/**
* The text height for the background image label. Only the text is painted inside, the token remains on the
* outside,
*/
private final int textHeight;
/*---------------------------------------------------------------------------------------------
* Class Variables
*-------------------------------------------------------------------------------------------*/
/**
* The size of an indicator.
*/
public static final Dimension INDICATOR_SIZE = new Dimension(18, 16);
/**
* The icon for the current indicator.
*/
public static final Icon CURRENT_INDICATOR_ICON = new ImageIcon(InitiativePanel.class.getClassLoader().getResource("com/t3/client/image/currentIndicator.png"));
/**
* Border used to show that an item is selected
*/
public static final Border SELECTED_BORDER = BorderFactory.createLineBorder(Color.BLACK);
/**
* Border used to show that an item is not selected
*/
public static final Border UNSELECTED_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
/**
* Border used to show that an item is selected
*/
public static final Border NAME_BORDER = BorderFactory.createEmptyBorder(2, 4, 3, 4);
/**
* The size of the ICON shown in the list renderer
*/
public static final int ICON_SIZE = 50;
/*---------------------------------------------------------------------------------------------
* Constructor
*-------------------------------------------------------------------------------------------*/
/**
* Create a renderer for the initiative panel.
*
* @param aPanel
* The initiative panel containing view state.
*/
public InitiativeListCellRenderer(InitiativePanel aPanel) {
// Set up the panel
panel = aPanel;
setLayout(new FormLayout("1px pref 1px pref:grow", "fill:pref"));
setBorder(SELECTED_BORDER);
setBackground(Color.WHITE);
// The current indicator
currentIndicator = new JLabel();
currentIndicator.setPreferredSize(INDICATOR_SIZE);
currentIndicator.setHorizontalAlignment(SwingConstants.CENTER);
currentIndicator.setVerticalAlignment(SwingConstants.CENTER);
add(currentIndicator, new CellConstraints(2, 1));
// And the name
name = new NameLabel();
name.setText("Ty");
name.setBorder(NAME_BORDER);
name.setFont(getFont().deriveFont(Font.BOLD));
textHeight = getFontMetrics(getFont()).getHeight();
add(name, new CellConstraints(4, 1, CellConstraints.LEFT, CellConstraints.CENTER));
validate();
}
/*---------------------------------------------------------------------------------------------
* ListCellRenderer Interface Methods
*-------------------------------------------------------------------------------------------*/
/**
* @see javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing.JList, java.lang.Object, int, boolean,
* boolean)
*/
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
// Set the background by type
Token token = null;
TokenInitiative ti = (TokenInitiative) value;
if (ti != null)
token = ti.getToken();
if (token == null) { // Can happen when deleting a token before all events have propagated
currentIndicator.setIcon(null);
name.setText(null);
name.setIcon(null);
setBorder(UNSELECTED_BORDER);
return this;
} // endif
backgroundImageLabel = token.isVisible() ? token.getType() == Token.Type.NPC ? GraphicsUtil.BLUE_LABEL : GraphicsUtil.GREY_LABEL : GraphicsUtil.DARK_GREY_LABEL;
name.setForeground(Color.BLACK);
// Show the indicator?
int currentIndex = panel.getList().getCurrent();
if (currentIndex >= 0 && ti == panel.getList().getTokenInitiative(currentIndex)) {
currentIndicator.setIcon(CURRENT_INDICATOR_ICON);
} else {
currentIndicator.setIcon(null);
} // endif
// Get the name string, add the state if displayed, then get the icon if needed
boolean initStateSecondLine = panel.isInitStateSecondLine() && panel.isShowInitState();
String sName = (initStateSecondLine ? "<html>" : "") + ti.getToken().getName();
if (TabletopTool.getFrame().getInitiativePanel().hasGMPermission() && token.getGMName() != null && token.getGMName().trim().length() != 0)
sName += " (" + token.getGMName().trim() + ")";
if (panel.isShowInitState() && ti.getState() != null)
sName += (initStateSecondLine ? "<br>" : " = ") + ti.getState();
if (initStateSecondLine)
sName += "</html>";
Icon icon = null;
if (panel.isShowTokens()) {
icon = ti.getDisplayIcon();
if (icon == null) {
icon = new InitiativeListIcon(ti);
ti.setDisplayIcon(icon);
} // endif
} // endif
name.setText(sName);
name.setIcon(icon);
// Align it properly
Alignment alignment = ti.isHolding() ? CellConstraints.RIGHT : CellConstraints.LEFT;
FormLayout layout = (FormLayout) getLayout();
layout.setConstraints(name, new CellConstraints(4, 1, alignment, CellConstraints.CENTER));
if (alignment == CellConstraints.RIGHT) {
name.setHorizontalTextPosition(SwingConstants.LEFT);
} else {
name.setHorizontalTextPosition(SwingConstants.RIGHT);
} // endif
// Selected?
if (isSelected) {
setBorder(SELECTED_BORDER);
} else {
setBorder(UNSELECTED_BORDER);
} // endif
return this;
}
/*---------------------------------------------------------------------------------------------
* NameLabel Inner Class
*-------------------------------------------------------------------------------------------*/
/**
* This label contains the sized background image for the name component.
*
* @author Jay
*/
public class NameLabel extends JLabel {
/**
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
@Override
protected void paintComponent(Graphics g) {
boolean initStateSecondLine = panel.isInitStateSecondLine() && panel.isShowInitState();
Dimension s = name.getSize();
int th = (textHeight + 2) * (initStateSecondLine ? 2 : 1);
backgroundImageLabel.renderLabel((Graphics2D) g, 0, (s.height - th) / 2, s.width, th);
super.paintComponent(g);
}
/**
* @see javax.swing.JComponent#getPreferredSize()
*/
@Override
public Dimension getPreferredSize() {
boolean initStateSecondLine = panel.isInitStateSecondLine() && panel.isShowInitState();
Dimension s = super.getPreferredSize();
int th = textHeight * (initStateSecondLine ? 2 : 1);
Insets insets = getInsets();
if (getIcon() != null)
th = Math.max(th, getIcon().getIconHeight());
s.height = th + insets.top + insets.bottom - 4;
return s;
}
}
/*---------------------------------------------------------------------------------------------
* InitiativeListIcon Inner Class
*-------------------------------------------------------------------------------------------*/
/**
* An icon that will show a token image and all of the states as needed.
*
* @author Jay
*/
public class InitiativeListIcon extends ImageIcon {
/** Bounds sent to the token state */
private final Rectangle bounds = new Rectangle(0, 0, ICON_SIZE, ICON_SIZE);
/** The token painted by this icon */
private final TokenInitiative tokenInitiative;
/** The image that is displayed when states are not being shown. */
private Image textTokenImage;
/** The image that is displayed when states are being shown. */
private Image stateTokenImage;
/** Size of the text only token */
private final int textTokenSize = Math.max(textHeight + 4, 16);
/**
* Create the image from the token and then build an icon suitable for displaying state.
*
* @param aTokenInitiative
* The initiative item being rendered
*/
public InitiativeListIcon(TokenInitiative aTokenInitiative) {
tokenInitiative = aTokenInitiative;
if (panel.isShowTokenStates()) {
stateTokenImage = scaleImage();
} else {
textTokenImage = scaleImage();
} // endif
}
/**
* Scale the token's image to fit in the allotted space for text or state painting.
*
* @return The properly scaled image.
*/
public Image scaleImage() {
Image image = ImageManager.getImageAndWait(tokenInitiative.getToken().getImageAssetId());
BufferedImage bi = ImageUtil.createCompatibleImage(getIconWidth(), getIconHeight(), Transparency.TRANSLUCENT);
Dimension d = new Dimension(image.getWidth(null), image.getHeight(null));
SwingUtil.constrainTo(d, getIconWidth(), getIconHeight());
Graphics2D g = bi.createGraphics();
g.drawImage(image, (getIconWidth() - d.width) / 2, (getIconHeight() - d.height) / 2, d.width, d.height, null);
setImage(bi);
return bi;
}
/**
* @see javax.swing.ImageIcon#getIconHeight()
*/
@Override
public int getIconHeight() {
return panel.isShowTokenStates() ? ICON_SIZE : textTokenSize;
}
/**
* @see javax.swing.ImageIcon#getIconWidth()
*/
@Override
public int getIconWidth() {
return panel.isShowTokenStates() ? ICON_SIZE : textTokenSize;
}
/**
* Paint the icon and then the image.
*
* @see javax.swing.ImageIcon#paintIcon(java.awt.Component, java.awt.Graphics, int, int)
*/
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
// Paint the halo if needed
Token token = tokenInitiative.getToken();
if (panel.isShowTokenStates() && token.hasHalo()) {
Graphics2D g2d = (Graphics2D) g;
Stroke oldStroke = g2d.getStroke();
Color oldColor = g.getColor();
g2d.setStroke(new BasicStroke(AppPreferences.getHaloLineWidth()));
g.setColor(token.getHaloColor());
g2d.draw(new Rectangle2D.Double(x, y, ICON_SIZE, ICON_SIZE));
g2d.setStroke(oldStroke);
g.setColor(oldColor);
} // endif
// Paint the icon, is that all that's needed?
if (panel.isShowTokenStates() && getImage() != stateTokenImage) {
if (stateTokenImage == null)
stateTokenImage = scaleImage();
setImage(stateTokenImage);
} else if (!panel.isShowTokenStates() && getImage() != textTokenImage) {
if (textTokenImage == null)
textTokenImage = scaleImage();
setImage(textTokenImage);
} // endif
super.paintIcon(c, g, x, y);
if (!panel.isShowTokenStates())
return;
// Paint all the states
g.translate(x, y);
Shape old = g.getClip();
g.setClip(bounds.intersection(old.getBounds()));
for (String state : TabletopTool.getCampaign().getTokenStatesMap().keySet()) {
BooleanTokenOverlay overlay = TabletopTool.getCampaign().getTokenStatesMap().get(state);
if (overlay == null || !overlay.showPlayer(token, TabletopTool.getPlayer()))
continue;
overlay.paintOverlay((Graphics2D) g, token, bounds, token.hasState(state));
} // endfor
for (String bar : TabletopTool.getCampaign().getTokenBarsMap().keySet()) {
Float barSet = token.getBar(bar);
BarTokenOverlay overlay = TabletopTool.getCampaign().getTokenBarsMap().get(bar);
if (overlay == null || !overlay.showPlayer(token, TabletopTool.getPlayer()))
continue;
overlay.paintOverlay((Graphics2D) g, token, bounds, barSet);
} // endfor
g.setClip(old);
g.translate(-x, -y);
}
}
}