// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.gui.layeritem; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.util.EnumMap; import javax.swing.SwingConstants; import org.infinity.gui.RenderCanvas; import org.infinity.resource.Viewable; import org.infinity.resource.graphics.ColorConvert; /** * Represents a game resource structure visually as a bitmap icon. */ public class IconLayerItem extends AbstractLayerItem implements LayerItemListener { private static final Image DefaultImage = ColorConvert.createCompatibleImage(1, 1, true); private EnumMap<ItemState, Image> images; private EnumMap<ItemState, FrameInfo> frames; private RenderCanvas rcCanvas; /** * Initialize object with default settings. */ public IconLayerItem() { this(null, null, null, null, null); } /** * Initialize object with the specified map location. * @param location Map location */ public IconLayerItem(Point location) { this(location, null, null, null, null); } /** * Initialize object with a specific map location and an associated viewable object. * @param location Map location * @param viewable Associated Viewable object */ public IconLayerItem(Point location, Viewable viewable) { this(location, viewable, null, null, null); } /** * Initialize object with a specific map location, associated Viewable and an additional text message. * @param location Map location * @param viewable Associated Viewable object * @param msg An arbitrary text message */ public IconLayerItem(Point location, Viewable viewable, String msg) { this(location, viewable, msg, null, null); } /** * Initialize object with a specific map location, associated Viewable, an additional text message * and an image for the visual representation. * @param location Map location * @param viewable Associated Viewable object * @param msg An arbitrary text message * @param image The image to display */ public IconLayerItem(Point location, Viewable viewable, String msg, Image image) { this(location, viewable, msg, image, null); } /** * Initialize object with a specific map location, associated Viewable, an additional text message, * an image for the visual representation and a locical center position within the icon. * @param location Map location * @param viewable Associated Viewable object * @param msg An arbitrary text message * @param image The image to display * @param center Logical center position within the icon */ public IconLayerItem(Point location, Viewable viewable, String msg, Image image, Point center) { super(location, viewable, msg); setLayout(new BorderLayout()); images = new EnumMap<ItemState, Image>(ItemState.class); frames = new EnumMap<ItemState, FrameInfo>(ItemState.class); rcCanvas = new FrameCanvas(this); rcCanvas.setHorizontalAlignment(SwingConstants.CENTER); rcCanvas.setVerticalAlignment(SwingConstants.CENTER); add(rcCanvas, BorderLayout.CENTER); setImage(ItemState.NORMAL, image); setCenterPosition(center); setCurrentImage(getItemState()); addLayerItemListener(this); } /** * Returns the image of the specified visual state. * @return The image of the specified visual state. */ public Image getImage(ItemState state) { if (state == null) { state = ItemState.NORMAL; } switch (state) { case HIGHLIGHTED: if (images.containsKey(ItemState.HIGHLIGHTED)) return images.get(ItemState.HIGHLIGHTED); case NORMAL: if (images.containsKey(ItemState.NORMAL)) return images.get(ItemState.NORMAL); } return DefaultImage; } /** * Sets the image for the specified visual state. * @param image The image to display in the specified visual state. */ public void setImage(ItemState state, Image image) { if (state != null) { if (image != null) { images.put(state, image); } else { images.remove(state); } updateSize(); } } /** * Returns the width of an optional frame around the item. * @param state The frame's visual state to get the frame width from. * @return The frame's width in pixels. */ public int getFrameWidth(ItemState state) { return getFrameInfo(state).getLineWidth(); } /** * Sets the width of an optional frame around the item. * @param state The frame's visual state to set the frame width for. * @param size The frame width in pixels. */ public void setFrameWidth(ItemState state, int size) { if (state != null) { if (frames.containsKey(state)) { frames.get(state).setLineWidth(size); } else { frames.put(state, new FrameInfo(size)); } } } /** * Returns the color of an optional frame around the item (Default color is white). * @param state The frame's visual state to get the frame color from. * @return The frame's color. */ public Color getFrameColor(ItemState state) { return getFrameInfo(state).getColor(); } /** * Sets the color of an optional frame around the item. * @param state The frame's visual state to set the frame color for. * @param color The frame color to set. */ public void setFrameColor(ItemState state, Color color) { if (state != null) { if (frames.containsKey(state)) { frames.get(state).setColor(color); } else { FrameInfo info = new FrameInfo(FrameInfo.getDefaultLineWidth()); info.setColor(color); frames.put(state, info); } } } /** * Returns whether a frame around the item will be displayed for the specified visual state. * @param state The frame's visual state to show the frame. * @return Whether the frame is shown for the specified visual state. */ public boolean getFrameEnabled(ItemState state) { return getFrameInfo(state).getEnabled(); } /** * Specify whether a frame around the item will be displayed for the specified visual state. * @param state The frame's visual state to set the display state. * @param show {@code true} if the frame should be displayed for the specified visual state * or {@code false} otherwise. */ public void setFrameEnabled(ItemState state, boolean show) { if (state != null) { if (frames.containsKey(state)) { frames.get(state).setEnabled(show); } else { FrameInfo info = new FrameInfo(FrameInfo.getDefaultLineWidth()); info.setEnabled(show); frames.put(state, info); } } } /** * Sets the logical center of the icon. * @return The logical center of the icon. */ public Point getCenterPosition() { return getLocationOffset(); } /** * Sets the logical center of the icon. * @param center The center position within the icon. */ public void setCenterPosition(Point center) { if (center == null) { center = new Point(0, 0); } if (!getLocationOffset().equals(center)) { Point distance = new Point(getLocationOffset().x - center.x, getLocationOffset().y - center.y); setLocationOffset(center); // updating component location Point loc = super.getLocation(); setLocation(loc.x + distance.x, loc.y + distance.y); validate(); } } //--------------------- Begin Interface LayerItemListener --------------------- @Override public void layerItemChanged(LayerItemEvent event) { if (event.getSource() == this) { setCurrentImage(getItemState()); } } //--------------------- End Interface LayerItemListener --------------------- // Returns whether the mouse cursor is over the relevant part of the component @Override protected boolean isMouseOver(Point pt) { BufferedImage image = ColorConvert.toBufferedImage(getCurrentImage(), true); if (image != null) { Rectangle region = new Rectangle((getSize().width - image.getWidth()) / 2, (getSize().height - image.getHeight()) / 2, image.getWidth(), image.getHeight()); if (region.contains(pt)) { int[] data = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); int color = data[(pt.y - region.y)*image.getWidth() + (pt.x - region.x)]; // (near) transparent pixels (alpha <= 16) are disregarded return ((color >>> 24) > 0x10); } else { return false; } } else { return super.isMouseOver(pt); } } private void updateSize() { Rectangle r = getBounds(); r.width = r.height = 0; for (final ItemState state: ItemState.values()) { Image image = getImage(state); r.width = Math.max(r.width, image.getWidth(null)); r.height = Math.max(r.height, image.getHeight(null)); } setPreferredSize(r.getSize()); setBounds(r); } private Image getCurrentImage() { return rcCanvas.getImage(); } private void setCurrentImage(ItemState state) { if (state != null) { rcCanvas.setImage(getImage(state)); } else { rcCanvas.setImage(null); } } private FrameInfo getFrameInfo(ItemState state) { if (state == null) { state = ItemState.NORMAL; } switch (state) { case HIGHLIGHTED: if (frames.containsKey(ItemState.HIGHLIGHTED)) return frames.get(ItemState.HIGHLIGHTED); case NORMAL: if (frames.containsKey(ItemState.NORMAL)) return frames.get(ItemState.NORMAL); } return new FrameInfo(FrameInfo.getDefaultLineWidth()); } //----------------------------- INNER CLASSES ----------------------------- // Extended JLabel to add the feature to show a frame around the component private static class FrameCanvas extends RenderCanvas { private final IconLayerItem parent; public FrameCanvas(IconLayerItem parent) { super(); this.parent = parent; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (parent != null) { FrameInfo info = parent.getFrameInfo(parent.getItemState()); if (info.getEnabled()) { int ofs = info.getLineWidth() / 2; Shape shp = new Rectangle(ofs, ofs, getWidth() - 2*ofs - 1, getHeight() - 2*ofs - 1); Graphics2D g2 = (Graphics2D)g; g2.setStroke(info.getStroke()); g2.setColor(info.getColor()); g2.draw(shp); } } } } // Stores information required to draw a customized frame around the component private static class FrameInfo { private boolean enabled; private Color color; private BasicStroke stroke; private static boolean getDefaultEnabled() { return false; } private static Color getDefaultColor() { return Color.WHITE; } private static int getDefaultLineWidth() { return 1; } private FrameInfo(int width) { setEnabled(getDefaultEnabled()); setColor(getDefaultColor()); setLineWidth(width); } private boolean getEnabled() { return enabled; } private void setEnabled(boolean enabled) { this.enabled = enabled; } private Color getColor() { return (color != null) ? color: getDefaultColor(); } private void setColor(Color c) { this.color = (c != null) ? c : getDefaultColor(); } private int getLineWidth() { return (stroke != null) ? (int)stroke.getLineWidth() : getDefaultLineWidth(); } private void setLineWidth(int width) { if (width < 1) { width = getDefaultLineWidth(); } if (stroke == null || (int)stroke.getLineWidth() != width) { stroke = new BasicStroke((float)width); } } private BasicStroke getStroke() { if (stroke == null) { stroke = new BasicStroke((float)getDefaultLineWidth()); } return stroke; } } }