package net.sf.colossus.gui; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.client.Client; import net.sf.colossus.game.Legion; /** * Class Marker implements the GUI for a legion marker. * * TODO this really represents a whole legion (since it shows the height), so * it probably should store a Legion object instead of the marker ID * TODO after carve out of GUI stuff Marker should probably not be accessed * by client at all - need cleanup with Legion ? * * @author David Ripton */ public final class Marker extends Chit { private static final Logger LOGGER = Logger.getLogger(Marker.class .getName()); private final Legion legion; private final boolean showHeight; private Font font; private int fontHeight; private int fontWidth; String hexLabel; private boolean highlight; /* * TODO get rid of markerId argument, derive it from legion argument * here by ourselves. * Not possible yet, at least in SplitLegion, PickMarker and RevealEvent * for legion destroyed in battle there is no child legion yet/any more. */ /** * Construct a marker without a client. * Use this constructor as a bit of documentation when * explicitly not wanting a height drawn on the Marker. * * Use case: The dialogs where legion height is not so important or legion * does not even exist (PickMarker, SplitLegion, in RevealEvent for the * destroyed legion) */ Marker(Legion legion, int scale, String id) { this(legion, scale, id, null, false, false); } /** * Construct a marker with a client (to be able to ask for * doNotInvertOption) but showHeight set to false and * specified inverted display (for defender) * * Use case: Marker on the battle map * * @param client A client, only used to ask for options */ Marker(Legion legion, int scale, String id, boolean inverted, Client client) { this(legion, scale, id, client, inverted, false); } /** * Construct a marker where height is shown - will be asked from legion. * Sometimes (on the master board, for example) heights should be shown, * and sometimes (in some dialogs, especially when there is no real legion * behind it (e.g. pickMarker, splitLegion)) they should be omitted * (or cannot even be asked). * * Use case: Mostly MasterBoard and some dialogs where height is * interesting: Concede/Flee, Negotiate and replyToProposal * * @param client A client, only used to ask for options */ Marker(Legion legion, int scale, String id, Client client, boolean showHeight) { this(legion, scale, id, client, false, showHeight); } /** * Construct a marker * @param id the marker label (like Bk05 or Bk05-Green) * @scale the Scale of chit * @param showHeight set true will add the height of the stack * @param inverted set to true (defender legion) will normally invert * the marker but NOT if doNotInvertDefender option is true */ private Marker(Legion legion, int scale, String id, Client client, boolean inverted, boolean showHeight) { super(scale, id, inverted, client); assert (!showHeight || legion != null) : "for showHeight true, " + "legion must not be null!"; this.legion = legion; this.showHeight = showHeight; setBackground(Color.BLACK); if (id.contains("Black") || (id.length() == 4 && id.startsWith("Bk"))) { setBorderColor(Color.white); } } /** this is only used by Battle markers marking entrances. */ void setLocation(Point point, String hexLabel) { setLocation(point); this.hexLabel = hexLabel; } /* Highlight borders of Markers on BattleMap */ void highlightMarker() { highlight = true; } void resetMarkerHighlight() { highlight = false; } @Override public void paintComponent(Graphics g) { LOGGER.log(Level.FINEST, "Painting marker"); Graphics2D g2 = (Graphics2D)g; if (highlight) { g2.setColor(Color.RED); Rectangle rect = getBounds(); g.fillRect(rect.x - 4, rect.y - 4, rect.width + 8, rect.height + 8); } super.paintComponent(g2); if (!showHeight) { return; //no height labels wanted } int legionHeight = legion.getHeight(); String legionHeightString = Integer.toString(legionHeight); LOGGER.log(Level.FINEST, "Height is " + legionHeightString); // Construct a font 1.5 times the size of the current font. Font oldFont = g.getFont(); if (font == null) { String name = oldFont.getName(); int size = oldFont.getSize(); int style = oldFont.getStyle(); font = new Font(name, style, 3 * size / 2); g.setFont(font); FontMetrics fontMetrics = g.getFontMetrics(); // XXX getAscent() seems to return too large a number // Test this 80% fudge factor on multiple platforms. fontHeight = 4 * fontMetrics.getAscent() / 5; fontWidth = fontMetrics.stringWidth(legionHeightString); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "New font set: " + font); LOGGER.log(Level.FINEST, "New font height: " + fontHeight); LOGGER.log(Level.FINEST, "New font width: " + fontWidth); } } else { g.setFont(font); } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Our rectangle is: " + rect); } int x = rect.x + rect.width * 3 / 4 - fontWidth / 2; int y = rect.y + rect.height * 2 / 3 + fontHeight / 2; // Provide a high-contrast background for the number. g.setColor(Color.white); g.fillRect(x, y - fontHeight, fontWidth, fontHeight); // Show height in black. g.setColor(Color.black); g.drawString(legionHeightString, x, y); // Restore the font. g.setFont(oldFont); } }