/**
*
* Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com
*
* This file is part of Freedomotic
*
* This Program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* This Program 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* Freedomotic; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
package com.freedomotic.jfrontend;
import com.freedomotic.app.Freedomotic;
import com.freedomotic.core.ResourcesManager;
import com.freedomotic.environment.EnvironmentLogic;
import com.freedomotic.environment.Room;
import com.freedomotic.environment.ZoneLogic;
import com.freedomotic.i18n.I18n;
import com.freedomotic.model.geometry.FreedomPoint;
import com.freedomotic.model.object.EnvObject;
import com.freedomotic.model.object.Representation;
import com.freedomotic.things.EnvObjectLogic;
import com.freedomotic.util.TopologyUtils;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Enrico Nicoletti
*/
public class Renderer extends Drawer implements MouseListener, MouseMotionListener {
private static final Logger LOG = LoggerFactory.getLogger(Renderer.class.getName());
private JavaDesktopFrontend plugin;
private Graphics graph;
private Graphics2D graph2D;
private AffineTransform originalRenderingContext;
private AffineTransform panelTransform;
private AffineTransform objectTransform = panelTransform;
private double widthRescale = 1.0;
private double heightRescale = 1.0;
private boolean backgroundChanged = true;
private int environmentWidth;
private int environmentHeight;
private static final int BORDER_X = 10; //the empty space around the map
private static final int BORDER_Y = 10; //the empty space around the map
private double CANVAS_WIDTH;
private double CANVAS_HEIGHT;
private static final int SNAP_TO_GRID = 20; //a grid of 20cm
/**
*
*/
public static final int HIGH_OPACITY = 180;
/**
*
*/
public static final int DEFAULT_OPACITY = 150;
/**
*
*/
public static final int LOW_OPACITY = 120;
private static Map<ZoneLogic, Color> zoneColors = new HashMap<ZoneLogic, Color>();
private final I18n I18n;
/**
*
*/
protected Color backgroundColor;
private EnvObjectLogic selectedObject;
private ArrayList<Indicator> indicators = new ArrayList<Indicator>();
private HashMap<String, Shape> cachedShapes = new HashMap<String, Shape>();
private boolean inDrag;
private boolean roomEditMode = false;
private FreedomPoint originalHandleLocation = null;
private ArrayList<Handle> handles = new ArrayList<Handle>();
private ZoneLogic selectedZone;
/**
*
*/
protected CalloutsUpdater calloutsUpdater;
private boolean objectEditMode = false;
private Point messageCorner = new Point(50, 50);
private Dimension dragDiff = null;
private EnvironmentLogic currEnv;
private BufferedImage background;
private Map<EnvObjectLogic, ObjectEditor> objEditorPanels = new HashMap<EnvObjectLogic, ObjectEditor>();
/**
*
* @param master
*/
public Renderer(JavaDesktopFrontend master) {
this.plugin = master;
this.I18n = plugin.getApi().getI18n();
environmentWidth = (int) getEnvironments().get(0).getPojo().getWidth();
environmentHeight = (int) getEnvironments().get(0).getPojo().getHeight();
CANVAS_WIDTH = environmentWidth + (BORDER_X * 2);
CANVAS_HEIGHT = environmentHeight + (BORDER_Y * 2);
backgroundColor = TopologyUtils.convertColorToAWT(getEnvironments().get(0).getPojo().getBackgroundColor());
calloutsUpdater = new CalloutsUpdater(this, 900);
currEnv = getEnvironments().get(0);
ResourcesManager.clear();
clear();
addCustomMouseListener();
addCustomMouseMotionListener();
setBackground(backgroundColor);
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
backgroundChanged = true;
findRescaleFactor();
}
});
repaint();
}
private List<EnvironmentLogic> getEnvironments() {
return plugin.getApi().environments().findAll();
}
/**
* Opens object editor GUI.
*
* @param obj the object to edit
*/
public void openObjEditor(EnvObjectLogic obj) {
if (objEditorPanels.containsKey(obj)) {
if (objEditorPanels.get(obj) == null) {
objEditorPanels.remove(obj);
objEditorPanels.put(obj, createNewObjectEditor(obj));
}
} else {
objEditorPanels.put(obj, createNewObjectEditor(obj));
}
final ObjectEditor currEditorPanel = objEditorPanels.get(obj);
currEditorPanel.setVisible(true);
currEditorPanel.toFront();
}
private ObjectEditor createNewObjectEditor(final EnvObjectLogic o) {
final ObjectEditor oe = new ObjectEditor(o);
oe.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
try {
objEditorPanels.remove(o);
} catch (Exception ex) {
LOG.error("Cannot unload object editor frame", ex);
}
}
@Override
public void windowClosed(WindowEvent e) {
try {
if (objEditorPanels.containsKey(o)) {
objEditorPanels.remove(o);
}
} catch (Exception ex) {
LOG.error("Cannot unload object editor frame", ex);
}
}
});
return oe;
}
/**
*
* @return
*/
public Map<EnvObjectLogic, ObjectEditor> getOpenThingEditors() {
return objEditorPanels;
}
/**
* Returns the current environment.
*
* @return the current environment
*/
@Override
public EnvironmentLogic getCurrEnv() {
return this.currEnv;
}
/**
* Sets the current environment.
*
* @param env
*/
@Override
public void setCurrEnv(EnvironmentLogic env) {
this.currEnv = env;
updateEnvRelatedVars();
setNeedRepaint(true);
}
/**
*
*/
public void updateEnvRelatedVars() {
environmentWidth = currEnv.getPojo().getWidth();
environmentHeight = currEnv.getPojo().getHeight();
backgroundColor = TopologyUtils.convertColorToAWT(currEnv.getPojo().getBackgroundColor());
}
/**
*
* @return
*/
protected EnvObjectLogic getSelectedObject() {
return selectedObject;
}
/**
*
* @param shape
* @param color
*/
protected void addIndicator(Shape shape, Color color) {
indicators.add(new Indicator(shape, color));
}
/**
*
* @param shape
*/
protected void addIndicator(Shape shape) {
indicators.add(new Indicator(shape));
}
/**
*
*/
protected void removeIndicators() {
indicators.clear();
selectedObject = null;
setNeedRepaint(false);
}
/**
*
* @return
*/
@Override
public ZoneLogic getSelectedZone() {
return selectedZone;
}
/**
*
* @param selectedZone
*/
@Override
public void setSelectedZone(ZoneLogic selectedZone) {
removeIndicators();
addIndicator(TopologyUtils.convertToAWT(selectedZone.getPojo().getShape()));
this.selectedZone = selectedZone;
createHandles(selectedZone);
//highlight the other rooms
for (Room room : getCurrEnv().getRooms()) {
if (room != selectedZone) {
if (zoneColors.get(room) == null) {
zoneColors.put(room, new Color(rand(0, 255), rand(0, 255), rand(0, 255), LOW_OPACITY));
}
addIndicator(TopologyUtils.convertToAWT(room.getPojo().getShape()), zoneColors.get(room));
}
}
}
private void addCustomMouseListener() {
addMouseListener(this);
}
private void addCustomMouseMotionListener() {
addMouseMotionListener(this);
}
/**
*
* @param repaintBackground
*/
@Override
public synchronized void setNeedRepaint(boolean repaintBackground) {
backgroundChanged = repaintBackground;
this.repaint();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Iterator it = getOpenThingEditors().entrySet().iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
EnvObjectLogic thing = (EnvObjectLogic) entry.getKey();
ObjectEditor editor = (ObjectEditor) entry.getValue();
//The object may have changed, refresh this panel
//Both null checks are REQUIRED!
if (thing != null && thing.getPojo() != null) {
editor = new ObjectEditor(thing);
} else {
it.remove();
}
}
}
});
}
private void renderIndicators() {
for (Indicator i : indicators) {
if (i.getShape() instanceof Polygon) {
getContext().setColor(i.getColor().darker());
getContext().drawPolygon((Polygon) i.getShape());
getContext().setColor(i.getColor());
getContext().fillPolygon((Polygon) i.getShape());
}
}
}
/**
*
*/
public void prepareBackground() {
}
public void renderEnvironment() {
}
public void renderWalls() {
}
public void prepareForeground() {
}
public void renderObjects() {
}
public void renderPeople() {
}
public void renderZones() {
}
/**
*
* @param obj
*/
public void mouseEntersObject(EnvObjectLogic obj) {
}
/**
*
* @param obj
*/
public void mouseExitsObject(EnvObjectLogic obj) {
}
/**
*
* @param obj
*/
public void mouseClickObject(EnvObjectLogic obj) {
}
private void paintEnvironmentLayer(Graphics g) {
setContext(g); //painting on an image, not rendered directly on jpanel
graph2D = (Graphics2D) getContext();
//painting uniform background
getContext().setColor(backgroundColor);
getContext().fillRect(0,
0,
this.getWidth(),
this.getHeight());
graph2D.scale(widthRescale, heightRescale);
try {
//translating the environment to mach the trasparent area in wall image
graph2D.translate(BORDER_X, BORDER_Y);
prepareBackground();
renderEnvironment();
renderZones();
prepareForeground();
//go to point 0,0
graph2D.translate(-BORDER_X, -BORDER_Y);
//render the wall image
renderWalls();
graph2D.translate(BORDER_X, BORDER_Y);
if (!roomEditMode) {
//selection markers
renderIndicators();
renderObjects();
} else {
renderIndicators();
renderHandles();
}
} catch (Exception e) {
LOG.error("Error while painting environment");
LOG.error(Freedomotic.getStackTraceInfo(e));
} finally {
restoreTransformContext();
}
}
/**
*
* @param g
*/
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = setRenderingQuality(g);
super.paintComponent(g2);
//long start = System.currentTimeMillis();
if (backgroundChanged) {
backgroundChanged = false;
synchronized (this) {
new Runnable() {
@Override
public void run() {
BufferedImage bkg = createDrawableCanvas();
paintEnvironmentLayer(setRenderingQuality(bkg.createGraphics()));
background = bkg;
}
}.run();
}
}
setContext(g2);
getContext().drawImage(background,
0,
0,
this);
createSceneTransformContext(getContext());
renderPeople();
renderCalloutsLayer();
restoreTransformContext();
//long end = System.currentTimeMillis();
//Freedomotic.logger.severe("Repainting process takes " + (end-start) + "ms");
}
private Graphics2D setRenderingQuality(Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
if (g2 != null) {
//g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
}
return g2;
}
private void renderCalloutsLayer() {
int numOfInfoLines = 0;
int offset = 0;
// Print mouse coordinates
Callout mousePointer = calloutsUpdater.getMousePointerCallout();
if (mousePointer != null) {
drawString(mousePointer.getText(),
(int) mousePointer.getPosition().getX(),
(int) mousePointer.getPosition().getY(),
(float) 0.0,
mousePointer.getColor());
}
// Print all other callouts
synchronized (calloutsUpdater.getPrintableCallouts()) {
for (Callout callout : calloutsUpdater.getPrintableCallouts()) {
//display multiple info callouts on different lines
if (callout.getGroup().equalsIgnoreCase("info")) {
offset = (numOfInfoLines * 50);
numOfInfoLines++;
drawString(callout.getText(),
(int) callout.getPosition().getX(),
(int) callout.getPosition().getY() + offset,
(float) 0.0,
callout.getColor());
} else {
drawString(callout.getText(),
(int) callout.getPosition().getX(),
(int) callout.getPosition().getY(),
(float) 0.0,
callout.getColor());
}
//limit to the last 10 most recent lines
if (numOfInfoLines > 10) {
return;
}
}
}
}
/**
*
* @param callout
*/
@Override
public void createCallout(Callout callout) {
calloutsUpdater.addCallout(callout);
}
private void createSceneTransformContext(Graphics g) {
graph2D = (Graphics2D) getContext();
originalRenderingContext = graph2D.getTransform();
AffineTransform newRenderingContext = (AffineTransform) (originalRenderingContext.clone());
newRenderingContext.scale(widthRescale, heightRescale);
newRenderingContext.translate(BORDER_X, BORDER_Y);
graph2D.setTransform(newRenderingContext);
}
/**
*
* @return
*/
protected Graphics getContext() {
return graph;
}
private void setContext(Graphics g) {
this.graph = g;
}
/**
*
* @return
*/
protected Graphics2D getRenderingContext() {
return graph2D;
}
/**
*
*/
protected void restoreTransformContext() {
try {
graph2D.setTransform(originalRenderingContext);
} catch (Exception e) {
}
}
private void findRescaleFactor() {
widthRescale = (double) ((double) this.getWidth() / (double) CANVAS_WIDTH);
heightRescale = (double) ((double) this.getHeight() / (double) CANVAS_HEIGHT);
if (widthRescale < heightRescale) {
heightRescale = widthRescale;
} else {
widthRescale = heightRescale;
}
}
/**
*
* @param obj
* @return
*/
protected AffineTransform setTransformContextFor(EnvObject obj) {
Graphics2D tmpGraph = (Graphics2D) getContext();
panelTransform = tmpGraph.getTransform();
AffineTransform newAt = (AffineTransform) (panelTransform.clone());
int x = (int) obj.getCurrentRepresentation().getOffset().getX();
int y = (int) obj.getCurrentRepresentation().getOffset().getY();
newAt.translate(x, y);
newAt.rotate(Math.toRadians(obj.getCurrentRepresentation().getRotation()));
tmpGraph.setTransform(newAt);
objectTransform = newAt;
return newAt;
}
/**
*
*/
protected void invalidateAnyTransform() {
Graphics2D tmpGraph = (Graphics2D) getContext();
tmpGraph.setTransform(panelTransform);
}
private void invalidateLastTransform() {
Graphics2D tmpGraph = (Graphics2D) getContext();
tmpGraph.setTransform(objectTransform);
}
private BufferedImage createDrawableCanvas() {
BufferedImage img = null;
try {
img = new BufferedImage(getWidth(),
getHeight(),
BufferedImage.TYPE_INT_ARGB);
} catch (Exception e) {
LOG.error(Freedomotic.getStackTraceInfo(e));
}
return img;
}
/**
*
* @param shape
* @param offset
* @return
*/
public static Shape getTranslatedShape(final Shape shape, Point offset) {
if (shape == null) {
throw new IllegalArgumentException("Null 'shape' argument.");
}
final AffineTransform transform = AffineTransform.getTranslateInstance(offset.getX(),
offset.getY());
return transform.createTransformedShape(shape);
}
/**
*
* @param shape
* @param rotation
* @return
*/
public static Shape getRotatedShape(Shape shape, double rotation) {
AffineTransform localAT = null;
Shape localShape = null;
localAT = AffineTransform.getRotateInstance(Math.toRadians(rotation),
shape.getBounds().getX(),
shape.getBounds().getY());
localShape = localAT.createTransformedShape(shape);
return localShape;
}
/**
*
* @param textureFile
* @param shape
*/
protected void paintTexture(String textureFile, Shape shape) {
BufferedImage img = null;
Graphics2D g2 = (Graphics2D) getContext();
if (textureFile != null) {
img = ResourcesManager.getResource(textureFile);
if (img != null) {
Rectangle2D replicationPath = new Rectangle2D.Double(0,
0,
img.getWidth(),
img.getHeight());
TexturePaint texture = new TexturePaint(img, replicationPath);
g2.setPaint(texture);
g2.fill(shape);
}
}
}
/**
*
* @param icon
* @param x
* @param y
* @param dimension
*/
protected void paintImageCentredOnCoords(String icon, int x, int y, Dimension dimension) {
BufferedImage img = null;
img = ResourcesManager.getResource(icon, (int) dimension.getWidth(), (int) dimension.getHeight());
if (img != null) {
getContext().drawImage(img, x - (img.getWidth() / 2), y - (img.getHeight() / 2), this);
}
}
/**
*
* @param img
* @param x
* @param y
*/
protected void paintImageCentredOnCoords(BufferedImage img, int x, int y) {
if (img != null) {
getContext().drawImage(img, x - (img.getWidth() / 2), y - (img.getHeight() / 2), this);
}
}
/**
*
* @param obj
* @throws RuntimeException
*/
protected void paintImage(EnvObject obj) {
BufferedImage img = ResourcesManager.getResource(
obj.getCurrentRepresentation().getIcon(), -1, -1); //-1 means no resizeing
if (img != null) {
getContext().drawImage(img, 0, 0, this);
} else {
throw new RuntimeException("Cannot find image " + obj.getCurrentRepresentation().getIcon() + " for object " + obj.getName());
}
}
/**
*
* @param img
*/
protected void paintImage(BufferedImage img) {
getContext().drawImage(img, 0, 0, this);
}
private void drawString(String text, int x, int y, float angle, Color color) {
Graphics2D localGraph = (Graphics2D) getContext();
final int BORDER = 10;
Font font = new Font("SansSerif", Font.PLAIN, 25);
localGraph.setFont(font);
//parse lines
String[] lines = text.trim().split("\n");
int longest = 0;
int maxChar = 0;
for (int k = 0; k < lines.length; k++) {
if (lines[k].length() > maxChar) {
maxChar = lines[k].length();
longest = k;
}
}
//draw background
AffineTransform origAt = localGraph.getTransform();
AffineTransform newAt = (AffineTransform) (origAt.clone());
Rectangle2D rect = localGraph.getFontMetrics().getStringBounds(lines[longest], localGraph);
RoundRectangle2D round = new RoundRectangle2D.Double(rect.getX(),
rect.getY(), rect.getWidth() + (BORDER * 2),
(rect.getHeight() * lines.length) + (BORDER * 2), 25, 25);
newAt.rotate(Math.toRadians(angle),
x,
y);
localGraph.setTransform(newAt);
Shape shape = getTranslatedShape(round,
new Point(x, y));
Color transparent = new Color(color.getRed(),
color.getGreen(),
color.getBlue(),
190);
localGraph.setColor(transparent);
localGraph.fill(shape);
//draw single lines
y += (BORDER - 27);
for (int j = 0; j < lines.length; j++) {
if (!lines[j].trim().isEmpty()) {
y += 27;
localGraph.setColor(Color.white);
localGraph.drawString(lines[j], x + BORDER, y);
}
}
localGraph.setTransform(origAt);
}
/**
*
* @param clickPoint
* @return
*/
protected Point toRealCoords(Point clickPoint) {
int x = (int) (clickPoint.getX() / widthRescale) - BORDER_X;
int y = (int) (clickPoint.getY() / heightRescale) - BORDER_Y;
return new Point(x, y);
}
/**
*
* @param p
* @return
*/
protected EnvObjectLogic mouseOnObject(Point p) {
Point mousePointer = toRealCoords(p);
for (EnvObjectLogic logic : plugin.getApi().things().findByEnvironment(currEnv)) {
if (getCachedShape(logic).contains(mousePointer)) {
return logic;
}
}
return null;
}
/**
*
* @param p
* @return
*/
protected ZoneLogic mouseOnZone(Point p) {
Iterator it = currEnv.getZones().iterator();
boolean onZone = false;
while (it.hasNext()) {
ZoneLogic zone = (ZoneLogic) it.next();
if (zone.getPojo().isRoom()) {
Point mouse = toRealCoords(p);
onZone = TopologyUtils.contains(zone.getPojo().getShape(),
new FreedomPoint((int) mouse.getX(), (int) mouse.getY()));
if (onZone == true) {
return zone;
}
}
}
return null;
}
/**
*
* @param p
* @return
*/
protected Handle mouseOnHandle(Point p) {
Point mouse = toRealCoords(p);
for (Handle handle : handles) {
Rectangle rect = (Rectangle) handle.getHandle();
if (rect.contains(mouse)) {
return handle;
}
}
return null;
}
private Shape applyShapeModifiers(EnvObjectLogic object) {
EnvObject obj = object.getPojo();
int x = obj.getCurrentRepresentation().getOffset().getX();
int y = obj.getCurrentRepresentation().getOffset().getY();
Shape shape = TopologyUtils.convertToAWT(obj.getShape());
shape = getTranslatedShape(shape,
new Point(x, y));
shape = getRotatedShape(shape,
obj.getCurrentRepresentation().getRotation());
cachedShapes.put(object.getPojo().getUUID(),
shape);
return shape;
}
private void rebuildShapesCache() {
for (EnvObjectLogic obj : plugin.getApi().things().findByEnvironment(getCurrEnv())) {
rebuildShapeCache(obj);
}
}
private void rebuildShapeCache(EnvObjectLogic obj) {
applyShapeModifiers(obj);
}
/**
*
* @param obj
* @return
*/
protected Shape getCachedShape(EnvObjectLogic obj) {
if (cachedShapes.containsKey(obj.getPojo().getUUID())) {
return cachedShapes.get(obj.getPojo().getUUID());
} else {
return applyShapeModifiers(obj);
}
}
/**
*
* @param e
*/
@Override
public void mouseClicked(MouseEvent e) {
if (!roomEditMode) {
EnvObjectLogic obj = mouseOnObject(e.getPoint());
if (obj != null) {
//single click on an object
if (e.getButton() == MouseEvent.BUTTON1) {
if (e.getClickCount() == 1) {
mouseClickObject(obj);
} else {
//double click on an object
if (e.getClickCount() == 2) {
mouseDoubleClickObject(obj);
}
}
} else {
//right click on an object
if (e.getButton() == MouseEvent.BUTTON3) {
if (e.getClickCount() == 1) {
mouseRightClickObject(obj);
}
}
}
} else {
removeIndicators();
calloutsUpdater.clearAll();
}
} else { //if edit mode
removeIndicators();
calloutsUpdater.clearAll();
toRealCoords(e.getPoint());
//click on an handle in edit mode
Handle clickedHandle = mouseOnHandle(e.getPoint());
//single right click
if (clickedHandle != null) {
if ((e.getClickCount() == 1) && (e.getButton() == MouseEvent.BUTTON1)) {
clickedHandle.setSelected(true);
} else {
//double right click
if ((e.getClickCount() == 2) && (e.getButton() == MouseEvent.BUTTON1)) {
clickedHandle.addAdiacent();
setSelectedZone(clickedHandle.getZone());
} else {
//single left click
if ((e.getClickCount() == 1) && (e.getButton() == MouseEvent.BUTTON3)) {
clickedHandle.remove();
setSelectedZone(clickedHandle.getZone());
}
}
}
} else {
//click on a zone in edit mode if no handle is selected
ZoneLogic zone = mouseOnZone(e.getPoint());
if (zone != null) {
Callout callout = new Callout(this.getClass().getCanonicalName(), "info",
I18n.msg("room_zone_selected") + " [" + zone.getPojo().getName() + "]", 50, 150, 0, -1);
createCallout(callout);
setSelectedZone(zone);
} else {
handles.clear();
// createHandles(null);
}
}
setNeedRepaint(true);
}
}
/**
*
* @param e
*/
@Override
public void mousePressed(MouseEvent e) {
if (roomEditMode) {
Point mouse = toRealCoords(e.getPoint());
Iterator it = handles.iterator();
boolean found = false;
while (it.hasNext() && !found) {
Handle entry = (Handle) it.next();
entry.setSelected(false);
Rectangle handle = (Rectangle) entry.getHandle();
if (handle.contains(mouse)) {
entry.setSelected(true);
found = true;
}
}
}
}
/**
*
* @param e
*/
@Override
public void mouseReleased(MouseEvent e) {
if (inDrag) {
for (Handle handle : handles) {
handle.setSelected(false);
}
//stop dragging an object
if (objectEditMode && (selectedObject != null)) {
Point coords = toRealCoords(e.getPoint());
selectedObject.setLocation((int) (coords.getX() - dragDiff.getWidth()),
(int) (coords.getY() - (int) dragDiff.getHeight()));
}
//check if rooms overlap
if (roomEditMode && (selectedZone) != null) {
setSelectedZone(selectedZone);
}
}
inDrag = false;
dragDiff = null;
selectedObject = null;
originalHandleLocation = null;
//removeIndicators();
rebuildShapesCache();
setNeedRepaint(true);
}
private List<ZoneLogic> overlappedRooms(ZoneLogic zone) {
List<ZoneLogic> overlapped = new ArrayList<ZoneLogic>();
Area currentZoneArea = new Area(TopologyUtils.convertToAWT(zone.getPojo().getShape()));
for (Room r : currEnv.getRooms()) {
if (!r.equals(selectedZone)) {
Shape testZoneShape = TopologyUtils.convertToAWT(r.getPojo().getShape());
Area testArea = new Area(testZoneShape);
testArea.intersect(currentZoneArea);
if (!testArea.isEmpty()) {
overlapped.add(r);
}
}
}
return overlapped;
}
/**
*
* @param e
*/
@Override
public void mouseEntered(MouseEvent e) {
}
/**
*
* @param e
*/
@Override
public void mouseExited(MouseEvent e) {
}
/**
*
* @param e
*/
@Override
public void mouseDragged(MouseEvent e) {
inDrag = true;
Point coords = toRealCoords(e.getPoint());
if ((dragDiff == null) && (getSelectedObject() != null)) {
dragDiff = new Dimension((int) Math.abs(coords.getX()
- getSelectedObject().getPojo().getCurrentRepresentation().getOffset().getX()),
(int) Math.abs(coords.getY()
- getSelectedObject().getPojo().getCurrentRepresentation().getOffset().getY()));
}
int xSnapped = (int) coords.getX() - ((int) coords.getX() % SNAP_TO_GRID);
int ySnapped = (int) coords.getY() - ((int) coords.getY() % SNAP_TO_GRID);
//in object edit mode
if (objectEditMode && (getSelectedObject() != null)) {
for (Representation representation : getSelectedObject().getPojo().getRepresentations()) {
//move an object
representation.setOffset(xSnapped - (int) dragDiff.getWidth(),
ySnapped - (int) dragDiff.getHeight());
setNeedRepaint(true);
}
} else {
if (roomEditMode) {
removeIndicators();
Callout callout = new Callout(this.getClass().getCanonicalName(), "mouse",
selectedZone.getPojo().getName() + ": " + xSnapped + "cm," + ySnapped + "cm",
(int) coords.getX() + 50, (int) coords.getY() + 50, 0, -1);
createCallout(callout);
for (Handle handle : handles) {
//move the zone point
if (handle.isSelected()) {
if (null == originalHandleLocation) {
originalHandleLocation = new FreedomPoint(handle.getPoint().getX(), handle.getPoint().getY());
}
handle.move(xSnapped, ySnapped);
addIndicator(TopologyUtils.convertToAWT(selectedZone.getPojo().getShape()));
// add indicators for overlapping zones
selectedZone = handle.getZone();
for (ZoneLogic overlapped : overlappedRooms(selectedZone)) {
//mark the zone in red if it is overlapped
addIndicator(TopologyUtils.convertToAWT(overlapped.getPojo().getShape()), new Color(255, 0, 0, HIGH_OPACITY));
}
}
}
setNeedRepaint(true);
}
}
}
/**
*
* @param obj
*/
public void mouseDoubleClickObject(EnvObjectLogic obj) {
}
/**
*
* @param obj
*/
public void mouseRightClickObject(EnvObjectLogic obj) {
}
/**
*
* @param e
*/
@Override
public void mouseMoved(MouseEvent e) {
if (!roomEditMode) {
EnvObjectLogic obj = mouseOnObject(e.getPoint());
if ((obj == null) && (selectedObject != null)) {
removeIndicators();
mouseExitsObject(selectedObject);
calloutsUpdater.clear("object.description");
}
if (obj != null) {
//addIndicator(cachedShapes.get(obj));
if (obj != selectedObject) {
mouseEntersObject(obj);
selectedObject = obj;
}
}
} else { //in edit mode but no dragging
if (!inDrag) {
Point mouse = toRealCoords(e.getPoint());
//create a callout which says the coordinates of the mouse in the environment (centimeters)
Callout callout = new Callout(this.getClass().getCanonicalName(), "mouse",
(int) mouse.getX() + "cm," + (int) mouse.getY() + "cm", (int) mouse.getX(),
(int) mouse.getY(), 0, -1);
createCallout(callout);
repaint();
}
}
}
private void clear() {
this.indicators.clear();
calloutsUpdater.clearAll();
this.backgroundChanged = true;
}
/**
*
* @param forZone
*/
protected void createHandles(ZoneLogic forZone) {
handles.clear();
if (forZone != null) { //create for all zones
Iterator<FreedomPoint> it = forZone.getPojo().getShape().getPoints().iterator();
while (it.hasNext()) {
FreedomPoint corner = (FreedomPoint) it.next();
handles.add(new Handle(forZone, corner));
}
} else {
for (ZoneLogic zone : currEnv.getZones()) {
Iterator<FreedomPoint> it = zone.getPojo().getShape().getPoints().iterator();
while (it.hasNext()) {
FreedomPoint corner = (FreedomPoint) it.next();
handles.add(new Handle(zone, corner));
}
}
}
repaint();
}
private void renderHandles() {
for (Handle handle : handles) {
getContext().setColor(new Color(0, 0, 255, DEFAULT_OPACITY));
if (handle.isSelected()) {
getContext().setColor(new Color(255, 0, 0, DEFAULT_OPACITY));
}
getContext().fillRect((int) handle.getHandle().getBounds().getX(),
(int) handle.getHandle().getBounds().getY(),
(int) handle.getHandle().getBounds().getWidth(),
(int) handle.getHandle().getBounds().getHeight());
}
}
/**
*
* @param edit
*/
@Override
public void setRoomEditMode(boolean edit) {
roomEditMode = edit;
if (roomEditMode) {
Callout callout = new Callout(this.getClass().getCanonicalName(), "info",
I18n.msg("environment_editing_instructions") + ":\n"
+ "- " + I18n.msg("environment_editing_instructions_add_new_room") + "\n"
+ "- " + I18n.msg("environment_editing_instructions_change_room_shape") + "\n"
+ "- " + I18n.msg("environment_editing_instructions_remove_room") + "\n"
+ "- " + I18n.msg("environment_editing_instructions_create_draggable_point") + "\n"
+ "- " + I18n.msg("environment_editing_instructions_delete_draggable_point") + "\n", 100, 200, 0, -1);
createCallout(callout);
createHandles(null);
//find the first room and select it
Room selectedRoom = getCurrEnv().getRooms().get(0);
if (selectedRoom != null) {
setSelectedZone(selectedRoom);
}
} else {
handles.clear();
indicators.clear();
selectedZone = null;
calloutsUpdater.clearAll();
}
setNeedRepaint(true);
}
/**
*
* @return
*/
@Override
public boolean getRoomEditMode() {
return roomEditMode;
}
/**
*
* @return
*/
@Override
public boolean getObjectEditMode() {
return objectEditMode;
}
/**
*
*/
protected void removeSelectedHandles() {
for (Handle handle : handles) {
if (handle.isSelected()) {
handle.remove();
}
}
}
@Override
public void setObjectEditMode(boolean state) {
objectEditMode = state;
}
/**
*
* @return
*/
public Color getBackgroundColor() {
return backgroundColor;
}
private int rand(int min, int max) {
// NOTE: Usually this should be a field rather than a method
// variable so that it is not re-seeded every call.
Random rand = new Random();
// nextInt is normally exclusive of the top value,
// so add 1 to make it inclusive
int randomNum = rand.nextInt((max - min) + 1) + min;
return randomNum;
}
}