package pipe.actions.gui;
import pipe.controllers.application.PipeApplicationController;
import pipe.gui.PetriNetTab;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
/**
* Zoom UI which intercepts mouse presses on a zoomed panel and transforms them to their
* correct location
*/
@SuppressWarnings("serial")
public class ZoomUI extends LayerUI<JComponent> implements ZoomManager {
/**
* Message fired on a zoom out
*/
public static final String ZOOM_OUT_CHANGE_MESSAGE = "zoomOut";
/**
* Message fired on a zoom in
*/
public static final String ZOOM_IN_CHANGE_MESSAGE = "zoomIn";
/**
* Amount to zoom in and out by
*/
private final double zoomAmount;
/**
* Minimum scale allowed to zoom to
*/
private final double zoomMin;
/**
* ApplicationView that this zooming belongs for
* is used to get petri net tab
*/
private final PipeApplicationController controller;
/**
* Maximum scale allowed to zoom to
*/
private final double zoomMax;
/**
* Change support for firing events when percent is changed.
*/
protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
/**
* Zoom transformation 1 = unzoomed
*/
private double zoom = 1;
/**
* @param startingScale initialZoomScale where 1 = unzoomed
* @param zoomAmount amount to zoom in/out by
* @param zoomMax maximum allowed zoom value
* @param zoomMin minimum allowed zoom value
* @param controller controller
*/
public ZoomUI(double startingScale, double zoomAmount, double zoomMax, double zoomMin,
PipeApplicationController controller) {
zoom = startingScale;
this.zoomAmount = zoomAmount;
this.zoomMax = zoomMax;
this.zoomMin = zoomMin;
this.controller = controller;
}
/**
* Paints the component with the current zoom scale
* @param g graphics
* @param c component
*/
@Override
public void paint(Graphics g, JComponent c) {
g.clearRect(c.getX(), c.getY(), c.getWidth(), c.getHeight());
Graphics2D g2 = (Graphics2D) g;
g2.scale(zoom, zoom);
super.paint(g2, c);
}
/**
* Transforms zoomed mouse events to their unzoomed coordinates
*
* @param e event
* @param l component
*/
@Override
protected void processMouseEvent(MouseEvent e, JLayer<? extends JComponent> l) {
MouseEvent localEvent = translateToLayerCoordinates(e, l);
if (clickNotOutOfBounds(localEvent, l)) {
Component component = getComponentClickedOn(l, localEvent);
if (localEvent.getID() == MouseEvent.MOUSE_PRESSED) {
for (ActionListener listener : component.getListeners(ActionListener.class)) {
ActionEvent actionEvent = new ActionEvent(component, localEvent.getID(), "CLICK");
listener.actionPerformed(actionEvent);
}
for (MouseListener listener : component.getListeners(MouseListener.class)) {
listener.mousePressed(getNewMouseClickEvent(component, localEvent));
}
} else if (localEvent.getID() == MouseEvent.MOUSE_RELEASED) {
for (MouseListener listener : component.getListeners(MouseListener.class)) {
listener.mouseReleased(getNewMouseClickEvent(component, localEvent));
}
} else if (localEvent.getID() == MouseEvent.MOUSE_CLICKED) {
for (MouseListener listener : component.getListeners(MouseListener.class)) {
listener.mouseClicked(getNewMouseClickEvent(component, localEvent));
}
}
e.consume();
}
}
/**
* Translates the event to a zoomed event point
* @param e mouse event
* @param l component
*/
@Override
protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JComponent> l) {
MouseEvent localEvent = translateToLayerCoordinates(e, l);
if (clickNotOutOfBounds(localEvent, l)) {
Component component = getComponentClickedOn(l, localEvent);
if (localEvent.getID() == MouseEvent.MOUSE_MOVED) {
for (MouseMotionListener listener : component.getListeners(MouseMotionListener.class)) {
listener.mouseMoved(getNewMouseClickEvent(component, localEvent));
}
} else if (localEvent.getID() == MouseEvent.MOUSE_DRAGGED) {
for (MouseMotionListener listener : component.getListeners(MouseMotionListener.class)) {
listener.mouseDragged(getNewMouseClickEvent(component, localEvent));
}
}
}
e.consume();
}
/**
* Noop action
* @param e mouse event
* @param l component
*/
@Override
protected void processMouseWheelEvent(MouseWheelEvent e, JLayer<? extends JComponent> l) {
//No action needed
}
/**
* Install the UI
* @param c component
*/
@Override
public void installUI(JComponent c) {
super.installUI(c);
JLayer<? extends JComponent> jlayer = (JLayer<? extends JComponent>) c;
jlayer.setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
/**
* Uninstall the UI
* @param c component
*/
@Override
public void uninstallUI(JComponent c) {
JLayer<? extends JComponent> jlayer = (JLayer<? extends JComponent>) c;
jlayer.setLayerEventMask(0);
super.uninstallUI(c);
}
/**
* Add a listener for zoom updates
* @param listener to add
*/
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
/**
* Remove a listener from the zoom UI
* @param listener to remove
*/
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
/**
*
* @param event mouse event
* @param l component
* @return true if the event is within the component bounds
*/
private boolean clickNotOutOfBounds(MouseEvent event, JLayer<? extends JComponent> l) {
return getComponentClickedOn(l, event) != null;
}
/**
* Perform a zoom out of the canvas
*/
@Override
public void zoomOut() {
if (canZoomOut()) {
double old = zoom;
zoom -= zoomAmount;
changeSupport.firePropertyChange(ZOOM_OUT_CHANGE_MESSAGE, old, zoom);
}
}
/**
* @param l layer clicked
* @param e mouse event with coordinates releative to l
* @return component in l clicked on
*/
private Component getComponentClickedOn(JLayer<? extends JComponent> l, MouseEvent e) {
PetriNetTab tab = controller.getActiveTab();
Point coordinates = zoomedXY(e);
return tab.getComponentAt(coordinates.x, coordinates.y);
}
/**
* @param e mouse click event
* @return the events x y coordinates zoomed
*/
private Point zoomedXY(MouseEvent e) {
int x = e.getX() == 0 ? 0 : (int) (e.getX() / zoom);
int y = e.getY() == 0 ? 0 : (int) (e.getY() / zoom);
return new Point(x, y);
}
/**
* @param e mouse event
* @param layer component
* @return a new event with x y pointing to the coordinate space of the layer
* rather than the whole application
*/
private MouseEvent translateToLayerCoordinates(MouseEvent e, JLayer<? extends JComponent> layer) {
PetriNetTab tab = controller.getActiveTab();
return SwingUtilities.convertMouseEvent(e.getComponent(), e, tab);
}
/**
*
* @param component clicked
* @param mouseEvent mouse event
* @return translated mouse click event
*/
private MouseEvent getNewMouseClickEvent(Component component, MouseEvent mouseEvent) {
Point coordinates = zoomedXY(mouseEvent);
return new MouseEvent(component, mouseEvent.getID(), mouseEvent.getWhen(), mouseEvent.getModifiers(),
coordinates.x, coordinates.y, mouseEvent.getClickCount(), mouseEvent.isPopupTrigger(),
mouseEvent.getButton());
}
/**
*
* @return the zoom scale as a percentage e.g. 20%, 100%, 120%
*/
@Override
public int getPercentageZoom() {
return (int) (zoom * 100);
}
/**
*
* @return the scale of the zoom e.g. 0.2, 1.0, 1.2
*/
@Override
public double getScale() {
return zoom;
}
/**
*
* @return true if can zoom out any further
*/
@Override
public boolean canZoomOut() {
return zoom - zoomAmount >= zoomMin;
}
/**
* Performs the zoom in on the canvas
*/
@Override
public void zoomIn() {
if (canZoomIn()) {
double old = zoom;
zoom += zoomAmount;
changeSupport.firePropertyChange(ZOOM_IN_CHANGE_MESSAGE, old, zoom);
}
}
/**
*
* @return true if can zoom in any further
*/
@Override
public boolean canZoomIn() {
return zoom + zoomAmount <= zoomMax;
}
}