diff --git a/src/main/org/insa/graphics/drawing/components/BasicDrawing.java b/src/main/org/insa/graphics/drawing/components/BasicDrawing.java index 75a3ee8..d1ace53 100644 --- a/src/main/org/insa/graphics/drawing/components/BasicDrawing.java +++ b/src/main/org/insa/graphics/drawing/components/BasicDrawing.java @@ -5,18 +5,19 @@ import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; +import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import javax.swing.JPanel; @@ -284,7 +285,10 @@ public class BasicDrawing extends JPanel implements Drawing { private List overlays = Collections.synchronizedList(new ArrayList()); // Mapping DrawingClickListener -> MouseEventListener - private Map listenerMapping = new IdentityHashMap<>(); + private List drawingClickListeners = new ArrayList<>(); + + // Zoom controls + private MapZoomControls zoomControls; /** * Create a new BasicDrawing. @@ -292,11 +296,52 @@ public class BasicDrawing extends JPanel implements Drawing { */ public BasicDrawing() { this.zoomAndPanListener = new ZoomAndPanListener(this, ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2); + + // Try... + try { + this.zoomControls = new MapZoomControls(this, 0, ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20); + this.zoomControls.addZoomInListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + zoomAndPanListener.zoomIn(); + } + }); + this.zoomControls.addZoomOutListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + zoomAndPanListener.zoomOut(); + } + }); + } + catch (IOException e) { + e.printStackTrace(); + } + + this.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent evt) { + if (zoomControls.contains(evt.getPoint())) { + return; + } + Point lonlat = null; + try { + lonlat = getLongitudeLatitude(evt); + } + catch (NoninvertibleTransformException e) { + return; + } + for (DrawingClickListener listener: drawingClickListeners) { + listener.mouseClicked(lonlat); + } + } + }); } @Override public void paintComponent(Graphics g1) { + super.paintComponent(g1); Graphics2D g = (Graphics2D) g1; + AffineTransform sTransform = g.getTransform(); g.clearRect(0, 0, getWidth(), getHeight()); g.setTransform(zoomAndPanListener.getCoordTransform()); @@ -311,6 +356,14 @@ public class BasicDrawing extends JPanel implements Drawing { overlay.draw(g); } } + + g.setTransform(sTransform); + if (this.zoomControls != null) { + this.zoomControls.setZoomLevel(this.zoomAndPanListener.getZoomLevel()); + this.zoomControls.draw(g, getWidth() - this.zoomControls.getWidth() - 20, + this.getHeight() - this.zoomControls.getHeight() - 10, this); + } + } /** @@ -355,24 +408,12 @@ public class BasicDrawing extends JPanel implements Drawing { @Override public void addDrawingClickListener(DrawingClickListener listener) { - MouseListener mListener = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent evt) { - try { - listener.mouseClicked(getLongitudeLatitude(evt)); - } - catch (NoninvertibleTransformException e) { - e.printStackTrace(); - } - } - }; - this.addMouseListener(mListener); - this.listenerMapping.put(listener, mListener); + this.drawingClickListeners.add(listener); } @Override public void removeDrawingClickListener(DrawingClickListener listener) { - this.removeMouseListener(this.listenerMapping.get(listener)); + this.drawingClickListeners.remove(listener); } @Override @@ -513,6 +554,7 @@ public class BasicDrawing extends JPanel implements Drawing { (this.getHeight() - this.height * scale) / 2); this.zoomAndPanListener.getCoordTransform().scale(scale, scale); this.zoomAndPanListener.setZoomLevel(0); + this.zoomControls.setZoomLevel(0); // Repaint this.repaint(); diff --git a/src/main/org/insa/graphics/drawing/components/MapViewDrawing.java b/src/main/org/insa/graphics/drawing/components/MapViewDrawing.java index 0365d1f..d5c1ea3 100644 --- a/src/main/org/insa/graphics/drawing/components/MapViewDrawing.java +++ b/src/main/org/insa/graphics/drawing/components/MapViewDrawing.java @@ -1,7 +1,12 @@ package org.insa.graphics.drawing.components; import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.UUID; @@ -207,6 +212,9 @@ public class MapViewDrawing extends MapView implements Drawing { // Tile size private int tileSize; + // Zoom controls + private MapZoomControls zoomControls; + public MapViewDrawing() { super(); Parameters.NUMBER_OF_THREADS = 2; @@ -219,6 +227,37 @@ public class MapViewDrawing extends MapView implements Drawing { this.setZoomLevelMin((byte) 0); this.setZoomLevelMax((byte) 20); + + // Try... + try { + this.zoomControls = new MapZoomControls(this, 0, 0, 20); + this.zoomControls.addZoomInListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + getModel().mapViewPosition.zoomIn(); + } + }); + this.zoomControls.addZoomOutListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + getModel().mapViewPosition.zoomOut(); + } + }); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void paint(Graphics graphics) { + super.paint(graphics); + if (this.zoomControls != null) { + this.zoomControls.setZoomLevel(this.getModel().mapViewPosition.getZoomLevel()); + this.zoomControls.draw((Graphics2D) graphics, getWidth() - this.zoomControls.getWidth() - 20, + this.getHeight() - this.zoomControls.getHeight() - 10, this); + } + } protected LatLong convertPoint(Point point) { @@ -232,6 +271,9 @@ public class MapViewDrawing extends MapView implements Drawing { @Override public boolean onTap(LatLong tapLatLong, org.mapsforge.core.model.Point layerXY, org.mapsforge.core.model.Point tapXY) { + if (zoomControls.contains(new java.awt.Point((int) tapXY.x, (int) tapXY.y))) { + return false; + } Point pt = new Point(tapLatLong.getLongitude(), tapLatLong.getLatitude()); for (DrawingClickListener listener: MapViewDrawing.this.drawingClickListeners) { listener.mouseClicked(pt); @@ -301,6 +343,7 @@ public class MapViewDrawing extends MapView implements Drawing { byte zoomLevel = LatLongUtils.zoomForBounds(model.mapViewDimension.getDimension(), boundingBox, model.displayModel.getTileSize()); model.mapViewPosition.setMapPosition(new MapPosition(boundingBox.getCenterPoint(), zoomLevel)); + zoomControls.setZoomLevel(zoomLevel); } } diff --git a/src/main/org/insa/graphics/drawing/components/MapZoomControls.java b/src/main/org/insa/graphics/drawing/components/MapZoomControls.java new file mode 100644 index 0000000..b892c7f --- /dev/null +++ b/src/main/org/insa/graphics/drawing/components/MapZoomControls.java @@ -0,0 +1,187 @@ +package org.insa.graphics.drawing.components; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.ImageObserver; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.imageio.ImageIO; + +public class MapZoomControls { + + // Default ID for action events + private static final int ZOOM_IN_ACTION_ID = 0x1; + private static final String ZOOM_IN_ACTION_NAME = "ZoomIn"; + + private static final int ZOOM_OUT_ACTION_ID = 0x2; + private static final String ZOOM_OUT_ACTION_NAME = "ZoomOut"; + + // Height + private static final int DEFAULT_HEIGHT = 20; + + // Default spacing between mark + private static final int DEFAULT_SPACING = 4; + + // Zoom ticks ratio from height (not the current one) + private static final double ZOOM_TICK_HEIGHT_RATIO = 0.3; + + // Zoom ticks color + private static final Color ZOOM_TICK_COLOR = Color.GRAY; + + // Current zoom ticks ratio from height + private static final double CURRENT_ZOOM_TICK_HEIGHT_RATIO = 0.8; + + // Zoom ticks color + private static final Color CURRENT_ZOOM_TICK_COLOR = new Color(25, 25, 25); + + // Use half mark or not + private boolean halfMark = true; + + private int currentLevel = 0; + private final int minLevel, maxLevel; + + // Zoom in/out image and their rectangles. + private final Image zoomIn, zoomOut; + private final Rectangle zoomInRect = new Rectangle(0, 0, 0, 0), zoomOutRect = new Rectangle(0, 0, 0, 0); + + // List of listeners + private final List zoomInListeners = new ArrayList<>(); + private final List zoomOutListeners = new ArrayList<>(); + + public MapZoomControls(Component component, final int defaultZoom, final int minZoom, final int maxZoom) + throws IOException { + + zoomIn = ImageIO.read(new File("zoomIn.png")).getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, + Image.SCALE_SMOOTH); + zoomOut = ImageIO.read(new File("zoomOut.png")).getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, + Image.SCALE_SMOOTH); + + this.currentLevel = defaultZoom; + this.minLevel = minZoom; + this.maxLevel = maxZoom; + + component.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (zoomInRect.contains(e.getPoint()) && currentLevel < maxLevel) { + currentLevel += 1; + for (ActionListener al: zoomInListeners) { + al.actionPerformed(new ActionEvent(this, ZOOM_IN_ACTION_ID, ZOOM_IN_ACTION_NAME)); + } + } + else if (zoomOutRect.contains(e.getPoint()) && currentLevel > minLevel) { + currentLevel -= 1; + for (ActionListener al: zoomOutListeners) { + al.actionPerformed(new ActionEvent(this, ZOOM_OUT_ACTION_ID, ZOOM_OUT_ACTION_NAME)); + } + } + component.repaint(); + } + }); + } + + /** + * Add a zoom-in listener. + * + * @param listener + */ + public void addZoomInListener(ActionListener listener) { + this.zoomInListeners.add(listener); + } + + /** + * Add a zoom-out listener. + * + * @param listener + */ + public void addZoomOutListener(ActionListener listener) { + this.zoomOutListeners.add(listener); + } + + /** + * @return the current zoom level. + */ + public int getZoomLevel() { + return this.currentLevel; + } + + /** + * Set the current zoom level. + * + * @param level + */ + public void setZoomLevel(int level) { + this.currentLevel = level; + } + + /** + * @return Height of this "component" when drawn. + */ + public int getHeight() { + return DEFAULT_HEIGHT; + } + + /** + * @return Width of this "component" when drawn. + */ + public int getWidth() { + return DEFAULT_HEIGHT + 2 + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2 + DEFAULT_HEIGHT; + } + + /** + * Check if a point is contained inside an element of this zoom controls, useful + * to avoid spurious click listeners. + * + * @param point + * + * @return true if the given point correspond to an element of this zoom + * controls. + */ + public boolean contains(Point point) { + return zoomInRect.contains(point) || zoomOutRect.contains(point); + } + + protected void draw(Graphics2D g, int xoffset, int yoffset, ImageObserver observer) { + + int height = getHeight(); + + // Draw icon + g.drawImage(zoomOut, xoffset, yoffset, observer); + zoomOutRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT); + + // Draw ticks + xoffset += DEFAULT_HEIGHT + 2; + g.setColor(ZOOM_TICK_COLOR); + g.drawLine(xoffset, yoffset + height / 2, xoffset + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1, + yoffset + height / 2); + for (int i = 0; i <= (this.maxLevel - this.minLevel); i += halfMark ? 2 : 1) { + g.drawLine(xoffset + i * DEFAULT_SPACING, yoffset + (int) (height * (1 - ZOOM_TICK_HEIGHT_RATIO) / 2), + xoffset + i * DEFAULT_SPACING, yoffset + (int) (height * (1 + ZOOM_TICK_HEIGHT_RATIO) / 2)); + } + + // Draw current ticks + g.setColor(CURRENT_ZOOM_TICK_COLOR); + g.drawLine(xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING, + yoffset + (int) (height * (1 - CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2), + xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING, + yoffset + (int) (height * (1 + CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2)); + + xoffset += (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2; + + g.drawImage(zoomIn, xoffset, yoffset, observer); + zoomInRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT); + + } + +} diff --git a/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java b/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java index c52f06c..764861d 100644 --- a/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java +++ b/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java @@ -1,141 +1,176 @@ package org.insa.graphics.drawing.components; -import java.awt.*; -import java.awt.event.*; +import java.awt.Component; +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener { - public static final int DEFAULT_MIN_ZOOM_LEVEL = -20; - public static final int DEFAULT_MAX_ZOOM_LEVEL = 10; - public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2; + public static final int DEFAULT_MIN_ZOOM_LEVEL = -20; + public static final int DEFAULT_MAX_ZOOM_LEVEL = 10; + public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2; - private Component targetComponent; + private Component targetComponent; - private int zoomLevel = 0; - private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL; - private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL; - private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR; + private int zoomLevel = 0; + private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL; + private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL; + private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR; - private Point dragStartScreen; - private Point dragEndScreen; - private AffineTransform coordTransform = new AffineTransform(); + private Point dragStartScreen; + private Point dragEndScreen; + private AffineTransform coordTransform = new AffineTransform(); - public ZoomAndPanListener(Component targetComponent) { - this.targetComponent = targetComponent; - } + public ZoomAndPanListener(Component targetComponent) { + this.targetComponent = targetComponent; + } - public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel, double zoomMultiplicationFactor) { - this.targetComponent = targetComponent; - this.minZoomLevel = minZoomLevel; - this.maxZoomLevel = maxZoomLevel; - this.zoomMultiplicationFactor = zoomMultiplicationFactor; - } - - public void translate(double dx, double dy) { - coordTransform.translate(dx, dy); - targetComponent.repaint(); - } + public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel, + double zoomMultiplicationFactor) { + this.targetComponent = targetComponent; + this.minZoomLevel = minZoomLevel; + this.maxZoomLevel = maxZoomLevel; + this.zoomMultiplicationFactor = zoomMultiplicationFactor; + } + public void translate(double dx, double dy) { + coordTransform.translate(dx, dy); + targetComponent.repaint(); + } - public void mouseClicked(MouseEvent e) { - } + public void mouseClicked(MouseEvent e) { + } - public void mousePressed(MouseEvent e) { - dragStartScreen = e.getPoint(); - dragEndScreen = null; - } + public void mousePressed(MouseEvent e) { + dragStartScreen = e.getPoint(); + dragEndScreen = null; + } - public void mouseReleased(MouseEvent e) { -// moveCamera(e); - } + public void mouseReleased(MouseEvent e) { + } - public void mouseEntered(MouseEvent e) { - } + public void mouseEntered(MouseEvent e) { + } - public void mouseExited(MouseEvent e) { - } + public void mouseExited(MouseEvent e) { + } - public void mouseMoved(MouseEvent e) { - } + public void mouseMoved(MouseEvent e) { + } - public void mouseDragged(MouseEvent e) { - moveCamera(e); - } + public void mouseDragged(MouseEvent e) { + moveCamera(e); + } - public void mouseWheelMoved(MouseWheelEvent e) { - zoomCamera(e); - } + public void mouseWheelMoved(MouseWheelEvent e) { + zoomCamera(e); + } - private void moveCamera(MouseEvent e) { - try { - dragEndScreen = e.getPoint(); - Point2D.Float dragStart = transformPoint(dragStartScreen); - Point2D.Float dragEnd = transformPoint(dragEndScreen); - double dx = dragEnd.getX() - dragStart.getX(); - double dy = dragEnd.getY() - dragStart.getY(); - coordTransform.translate(dx, dy); - dragStartScreen = dragEndScreen; - dragEndScreen = null; - targetComponent.repaint(); - } catch (NoninvertibleTransformException ex) { - ex.printStackTrace(); - } - } + private void moveCamera(MouseEvent e) { + try { + dragEndScreen = e.getPoint(); + Point2D.Float dragStart = transformPoint(dragStartScreen); + Point2D.Float dragEnd = transformPoint(dragEndScreen); + double dx = dragEnd.getX() - dragStart.getX(); + double dy = dragEnd.getY() - dragStart.getY(); + coordTransform.translate(dx, dy); + dragStartScreen = dragEndScreen; + dragEndScreen = null; + targetComponent.repaint(); + } + catch (NoninvertibleTransformException ex) { + ex.printStackTrace(); + } + } - private void zoomCamera(MouseWheelEvent e) { - try { - int wheelRotation = e.getWheelRotation(); - Point p = e.getPoint(); - if (wheelRotation > 0) { - if (zoomLevel < maxZoomLevel) { - zoomLevel++; - Point2D p1 = transformPoint(p); - coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor); - Point2D p2 = transformPoint(p); - coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); - targetComponent.repaint(); - } - } else { - if (zoomLevel > minZoomLevel) { - zoomLevel--; - Point2D p1 = transformPoint(p); - coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor); - Point2D p2 = transformPoint(p); - coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); - targetComponent.repaint(); - } - } - } catch (NoninvertibleTransformException ex) { - ex.printStackTrace(); - } - } + private void zoomCamera(MouseWheelEvent e) { + try { + int wheelRotation = e.getWheelRotation(); + Point p = e.getPoint(); + if (wheelRotation < 0) { + if (zoomLevel < maxZoomLevel) { + zoomLevel++; + Point2D p1 = transformPoint(p); + coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor); + Point2D p2 = transformPoint(p); + coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); + targetComponent.repaint(); + } + } + else { + if (zoomLevel > minZoomLevel) { + zoomLevel--; + Point2D p1 = transformPoint(p); + coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor); + Point2D p2 = transformPoint(p); + coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); + targetComponent.repaint(); + } + } + } + catch (NoninvertibleTransformException ex) { + ex.printStackTrace(); + } + } - private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException { + private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException { - AffineTransform inverse = coordTransform.createInverse(); + AffineTransform inverse = coordTransform.createInverse(); - Point2D.Float p2 = new Point2D.Float(); - inverse.transform(p1, p2); - return p2; - } + Point2D.Float p2 = new Point2D.Float(); + inverse.transform(p1, p2); + return p2; + } - public int getZoomLevel() { - return zoomLevel; - } + public int getZoomLevel() { + return zoomLevel; + } + public void setZoomLevel(int zoomLevel) { + this.zoomLevel = zoomLevel; + } - public void setZoomLevel(int zoomLevel) { - this.zoomLevel = zoomLevel; - } + public void zoomIn() { + try { + Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2); + zoomLevel++; + Point2D p1 = transformPoint(p); + coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor); + Point2D p2 = transformPoint(p); + coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); + targetComponent.repaint(); + } + catch (NoninvertibleTransformException ex) { + ex.printStackTrace(); + } + } + public void zoomOut() { + try { + Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2); + zoomLevel--; + Point2D p1 = transformPoint(p); + coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor); + Point2D p2 = transformPoint(p); + coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); + targetComponent.repaint(); + } + catch (NoninvertibleTransformException ex) { + ex.printStackTrace(); + } + } - public AffineTransform getCoordTransform() { - return coordTransform; - } + public AffineTransform getCoordTransform() { + return coordTransform; + } - public void setCoordTransform(AffineTransform coordTransform) { - this.coordTransform = coordTransform; - } + public void setCoordTransform(AffineTransform coordTransform) { + this.coordTransform = coordTransform; + } } \ No newline at end of file diff --git a/zoomIn.png b/zoomIn.png new file mode 100644 index 0000000..52e80bd Binary files /dev/null and b/zoomIn.png differ diff --git a/zoomOut.png b/zoomOut.png new file mode 100644 index 0000000..1d01d90 Binary files /dev/null and b/zoomOut.png differ