/* * $Id$ * * Copyright (c) 2000-2007 by Rodney Kinney, Joel Uckelman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.build.module.map; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JLayeredPane; import javax.swing.JPanel; import javax.swing.border.EtchedBorder; import org.w3c.dom.Element; import VASSAL.build.AutoConfigurable; import VASSAL.build.Buildable; import VASSAL.build.Configurable; import VASSAL.build.GameModule; import VASSAL.build.module.GameComponent; import VASSAL.build.module.Map; import VASSAL.build.module.documentation.HelpFile; import VASSAL.command.Command; import VASSAL.configure.AutoConfigurer; import VASSAL.configure.ColorConfigurer; import VASSAL.configure.Configurer; import VASSAL.configure.ConfigurerFactory; import VASSAL.configure.IconConfigurer; import VASSAL.configure.VisibilityCondition; import VASSAL.counters.GamePiece; import VASSAL.i18n.ComponentI18nData; import VASSAL.i18n.Resources; import VASSAL.i18n.Translatable; import VASSAL.tools.KeyStrokeSource; import VASSAL.tools.LaunchButton; import VASSAL.tools.NamedKeyStroke; import VASSAL.tools.ScrollPane; /** * This is scaled version of a {@link Map} that gives an overview. * Users can navigate around the Map by clicking on the GlobalMap, which * draws a rectangular region of interest (ROI) indicating the current * viewable area in the map window. */ public class GlobalMap implements AutoConfigurable, GameComponent, Drawable { private static final long serialVersionUID = 2L; protected Map map; protected double scale = 0.19444444; // Zoom factor protected Color rectColor = Color.black; protected final LaunchButton launch; protected CounterDetailViewer mouseOverViewer; protected final ScrollPane scroll; protected final View view; protected ComponentI18nData myI18nData; public GlobalMap() { view = new View(); view.addMouseListener(view); scroll = new GlobalMapScrollPane(view); scroll.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); scroll.setAlignmentX(0.0f); scroll.setAlignmentY(0.0f); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { scroll.setVisible(!scroll.isVisible()); } }; launch = new LaunchButton(null, TOOLTIP, BUTTON_TEXT, HOTKEY, ICON_NAME, al); launch.setAttribute(TOOLTIP, "Show/Hide overview window"); launch.setAttribute(HOTKEY, NamedKeyStroke.getNamedKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK)); } /** * Expects to be added to a {@link Map}. Adds itself as a {@link * GameComponent} and a {@link Drawable}component */ public void addTo(Buildable b) { map = (Map) b; mouseOverViewer = new CounterViewer(); GameModule.getGameModule().getGameState().addGameComponent(this); GameModule.getGameModule().addKeyStrokeSource( new KeyStrokeSource(view, JComponent.WHEN_FOCUSED)); map.addDrawComponent(this); map.getToolBar().add(launch); if (b instanceof Translatable) { getI18nData().setOwningComponent((Translatable) b); } map.getLayeredPane().add(scroll, JLayeredPane.PALETTE_LAYER); } public void add(Buildable b) { } public void remove(Buildable b) { } public void removeFrom(Buildable b) { map = (Map) b; map.removeDrawComponent(this); map.getToolBar().remove(launch); GameModule.getGameModule().getGameState().removeGameComponent(this); map.getLayeredPane().remove(scroll); } public void build(Element e) { AutoConfigurable.Util.buildAttributes(e, this); } protected static final String SCALE = "scale"; protected static final String COLOR = "color"; protected static final String HOTKEY = "hotkey"; protected static final String ICON_NAME = "icon"; protected static final String TOOLTIP = "tooltip"; protected static final String BUTTON_TEXT = "buttonText"; protected static final String DEFAULT_ICON = "/images/overview.gif"; public String[] getAttributeNames() { return new String[] { TOOLTIP, BUTTON_TEXT, ICON_NAME, HOTKEY, SCALE, COLOR }; } public VisibilityCondition getAttributeVisibility(String name) { return null; } public void setAttribute(String key, Object value) { if (SCALE.equals(key)) { if (value instanceof String) { value = Double.valueOf((String) value); } scale = ((Double) value).doubleValue(); } else if (COLOR.equals(key)) { if (value instanceof String) { value = ColorConfigurer.stringToColor((String) value); } rectColor = (Color) value; } else { launch.setAttribute(key, value); } } public String getAttributeValueString(String key) { if (SCALE.equals(key)) { return String.valueOf(scale); } else if (COLOR.equals(key)) { return ColorConfigurer.colorToString(rectColor); } else { return launch.getAttributeValueString(key); } } public String[] getAttributeDescriptions() { return new String[] { Resources.getString(Resources.TOOLTIP_TEXT), Resources.getString(Resources.BUTTON_TEXT), Resources.getString(Resources.BUTTON_ICON), Resources.getString("Editor.GlobalMap.show_hide"), //$NON-NLS-1$ Resources.getString("Editor.GlobalMap.scale_factor"), //$NON-NLS-1$ Resources.getString("Editor.GlobalMap.hilight"), //$NON-NLS-1$ }; } public Class<?>[] getAttributeTypes() { return new Class<?>[] { String.class, String.class, IconConfig.class, NamedKeyStroke.class, Double.class, Color.class }; } public static class IconConfig implements ConfigurerFactory { public Configurer getConfigurer(AutoConfigurable c, String key, String name) { return new IconConfigurer(key, name, DEFAULT_ICON); } } public void draw(Graphics g, Map m) { view.repaint(); } public boolean drawAboveCounters() { return true; } /** * Transform a point from Map coordinates to coordinates in the overview * window * * @param p * @return */ public Point componentCoordinates(Point p) { return new Point((int) ((p.x - map.getEdgeBuffer().width) * scale), (int) ((p.y - map.getEdgeBuffer().height) * scale)); } /** * Transform a point from coordinates in the overview window to Map * coordinates * * @param p * @return */ public Point mapCoordinates(Point p) { return new Point( (int) Math.round(p.x / scale) + map.getEdgeBuffer().width, (int) Math.round(p.y / scale) + map.getEdgeBuffer().height); } public String getToolTipText(MouseEvent e) { return null; } public Command getRestoreCommand() { return null; } public void setup(boolean show) { if (show) { scroll.setMaximumSize(scroll.getPreferredSize()); } else { scroll.setVisible(false); } if (show && !map.getComponentsOf(CounterDetailViewer.class).isEmpty()) { view.addMouseListener(mouseOverViewer); view.addMouseMotionListener(mouseOverViewer); scroll.addKeyListener(mouseOverViewer); } else { view.removeMouseListener(mouseOverViewer); view.removeMouseMotionListener(mouseOverViewer); scroll.removeKeyListener(mouseOverViewer); } } public static String getConfigureTypeName() { return Resources.getString("Editor.GlobalMap.component_type"); //$NON-NLS-1$ } public String getConfigureName() { return null; } public Configurer getConfigurer() { return new AutoConfigurer(this); } public Configurable[] getConfigureComponents() { return new Configurable[0]; } public Class<?>[] getAllowableConfigureComponents() { return new Class<?>[0]; } public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { } public HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("Map.htm", "OverviewWindow"); } public org.w3c.dom.Element getBuildElement(org.w3c.dom.Document doc) { return AutoConfigurable.Util.getBuildElement(doc, this); } protected class CounterViewer extends CounterDetailViewer { public CounterViewer() { this.map = GlobalMap.this.map; this.view = GlobalMap.this.view; } protected List<GamePiece> getDisplayablePieces() { final Point oldPoint = currentMousePosition.getPoint(); final Point mapPoint = GlobalMap.this.map.componentCoordinates(mapCoordinates(oldPoint)); currentMousePosition.translatePoint(mapPoint.x - oldPoint.x, mapPoint.y - oldPoint.y); final List<GamePiece> l = super.getDisplayablePieces(); currentMousePosition.translatePoint(oldPoint.x - mapPoint.x, oldPoint.y - mapPoint.y); return l; } protected double getZoom() { return scale; } } /** * The scroll pane in which the map {@link View} is displayed. */ protected class GlobalMapScrollPane extends ScrollPane { private static final long serialVersionUID = 1L; public GlobalMapScrollPane(Component view) { super(view, ScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); } /** * @return The display size of the entire zoomed overview map */ public Dimension getPreferredSize() { final Dimension d = view.getPreferredSize(); final Insets i = getInsets(); d.width += i.left + i.right; d.height += i.top + i.bottom; return d; } /** * @return The maximum size of the zoomed overview map and scroll pane */ public Dimension getMaximumSize() { final Dimension d = getPreferredSize(); if (verticalScrollBar.isVisible()) { d.width += verticalScrollBar.getPreferredSize().width; } if (horizontalScrollBar.isVisible()) { d.height += horizontalScrollBar.getPreferredSize().height; } return d; } public void setBounds(Rectangle r) { final Dimension availSize = map.getView().getParent().getSize(); final Dimension viewSize = view.getPreferredSize(); final Insets i = getInsets(); viewSize.width += i.left + i.right; viewSize.height += i.top + i.bottom; final boolean hsbNeeded = availSize.width < viewSize.width; final boolean vsbNeeded = availSize.height < viewSize.height; final Dimension realSize = new Dimension(); if (availSize.width < viewSize.width) { realSize.width = availSize.width; } else if (vsbNeeded) { realSize.width = Math.min(availSize.width, viewSize.width + verticalScrollBar.getPreferredSize().width); } else { realSize.width = viewSize.width; } if (availSize.height < viewSize.height) { realSize.height = availSize.height; } else if (hsbNeeded) { realSize.height = Math.min(availSize.height, viewSize.height + horizontalScrollBar.getPreferredSize().height); } else { realSize.height = viewSize.height; } super.setBounds(0,0,realSize.width,realSize.height); } /** * This funcion is overridden to make sure that the parent layout * is redone when the GlobalMap is shown. */ public void setVisible(boolean visible) { super.setVisible(visible); if (visible) { final LayoutManager l = getParent().getLayout(); if (l instanceof Map.InsetLayout) { l.layoutContainer(getParent()); } } } } /** * The Map view that appears inside the ScrollPane */ protected class View extends JPanel implements MouseListener { private static final long serialVersionUID = 1L; @Override protected void paintComponent(Graphics g) { map.drawBoards(g, -Math.round((float) scale * map.getEdgeBuffer().width), -Math.round((float) scale * map.getEdgeBuffer().height), scale, this); for (GamePiece gp : map.getPieces()) { Point p = componentCoordinates(gp.getPosition()); gp.draw(g, p.x, p.y, this, scale); } mouseOverViewer.draw(g, map); // FIXME: use a Graphics2D for this // Draw a rectangle indicating the present viewing area g.setColor(rectColor); final Rectangle r = map.getView().getVisibleRect(); final Point ul = componentCoordinates(map.mapCoordinates(r.getLocation())); final int w = (int) (scale * r.width / map.getZoom()); final int h = (int) (scale * r.height / map.getZoom()); g.drawRect(ul.x, ul.y, w, h); g.drawRect(ul.x - 1, ul.y - 1, w + 2, h + 2); } public void mousePressed(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } public void mouseReleased(MouseEvent e) { map.centerAt(mapCoordinates(e.getPoint())); } public Dimension getPreferredSize() { return new Dimension( (int)((map.mapSize().width - 2*map.getEdgeBuffer().width) * scale), (int)((map.mapSize().height - 2*map.getEdgeBuffer().height) * scale)); } } public ComponentI18nData getI18nData() { if (myI18nData == null) { myI18nData = new ComponentI18nData(this, "GlobalMap"); } return myI18nData; } }