package rescuecore2.view;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Collection;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Color;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import rescuecore2.config.Config;
import rescuecore2.misc.gui.PanZoomListener;
import rescuecore2.misc.gui.ScreenTransform;
/**
A JComponent that shows a view of a world model.
*/
public abstract class ViewComponent extends JComponent {
private static final Color BACKGROUND = new Color(120, 120, 120);
private PanZoomListener panZoom;
private ScreenTransform transform;
private List<RenderedObject> renderedObjects;
private Set<ViewListener> listeners;
/**
Construct a new ViewComponent.
*/
protected ViewComponent() {
renderedObjects = new ArrayList<RenderedObject>();
listeners = new HashSet<ViewListener>();
ViewerMouseListener l = new ViewerMouseListener();
addMouseListener(l);
addMouseMotionListener(l);
panZoom = new PanZoomListener(this);
setBackground(BACKGROUND);
setOpaque(true);
}
/**
Initialise this view component. Default implementation does nothing.
@param config The system configuration.
*/
public void initialise(Config config) {
}
/**
Get the name of this view component. Default implementation calls toString().
@return The name of this view component.
*/
public String getViewerName() {
return toString();
}
/**
Disable the pan-zoom feature.
*/
public void disablePanZoom() {
removeMouseListener(panZoom);
removeMouseMotionListener(panZoom);
removeMouseWheelListener(panZoom);
transform.resetZoom();
repaint();
}
/**
Enable the pan-zoom feature.
*/
public void enablePanZoom() {
addMouseListener(panZoom);
addMouseMotionListener(panZoom);
addMouseWheelListener(panZoom);
}
/**
Add a view listener.
@param l The listener to add.
*/
public void addViewListener(ViewListener l) {
synchronized (listeners) {
listeners.add(l);
}
}
/**
Remove a view listener.
@param l The listener to remove.
*/
public void removeViewListener(ViewListener l) {
synchronized (listeners) {
listeners.remove(l);
}
}
/**
View a set of objects. Default implementation just calls repaint.
@param objects The objects to view.
*/
public void view(Object... objects) {
repaint();
}
@Override
public final void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
Insets insets = getInsets();
width -= insets.left + insets.right;
height -= insets.top + insets.bottom;
renderedObjects.clear();
transform.rescale(width, height);
Graphics2D copy = (Graphics2D)g.create(insets.left, insets.top, width, height);
if (isOpaque()) {
copy.setColor(getBackground());
copy.fillRect(0, 0, width, height);
}
renderedObjects.addAll(render(copy, transform, width, height));
}
/**
Render the world and return the set of RenderedObjects.
@param graphics The graphics to draw on. The origin is guaranteed to be at 0, 0.
@param transform The ScreenTransform that will convert world coordinates to screen coordinates.
@param width The width of the viewport.
@param height The height of the viewport.
@return The set of RenderedObjects.
*/
// CHECKSTYLE:OFF:HiddenField Supress bogus warning about transform hiding a field: it's not really hidden since this is an abstract method and transform is private.
protected abstract Collection<RenderedObject> render(Graphics2D graphics, ScreenTransform transform, int width, int height);
// CHECKSTYLE:ON:HiddenField
/**
Update the bounds of the view in world coordinates.
@param xMin The minimum x coordinate.
@param yMin The minimum y coordinate.
@param xMax The maximum x coordinate.
@param yMax The maximum y coordinate.
*/
protected void updateBounds(double xMin, double yMin, double xMax, double yMax) {
transform = new ScreenTransform(xMin, yMin, xMax, yMax);
panZoom.setScreenTransform(transform);
}
private List<RenderedObject> getObjectsAtPoint(int x, int y) {
List<RenderedObject> result = new ArrayList<RenderedObject>();
for (RenderedObject next : renderedObjects) {
Shape shape = next.getShape();
if (shape != null && shape.contains(x, y)) {
result.add(next);
}
}
return result;
}
private void fireObjectsClicked(List<RenderedObject> clicked) {
List<ViewListener> all = new ArrayList<ViewListener>();
synchronized (listeners) {
all.addAll(listeners);
}
for (ViewListener next : all) {
next.objectsClicked(this, clicked);
}
}
private void fireObjectsRollover(List<RenderedObject> objects) {
List<ViewListener> all = new ArrayList<ViewListener>();
synchronized (listeners) {
all.addAll(listeners);
}
for (ViewListener next : all) {
next.objectsRollover(this, objects);
}
}
private class ViewerMouseListener implements MouseListener, MouseMotionListener {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
fireObjectsClicked(getObjects(e));
}
}
@Override
public void mouseMoved(MouseEvent e) {
fireObjectsRollover(getObjects(e));
}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public void mouseDragged(MouseEvent e) {}
private List<RenderedObject> getObjects(MouseEvent e) {
Point p = e.getPoint();
Insets insets = getInsets();
return getObjectsAtPoint(p.x - insets.left, p.y - insets.top);
}
}
}