package pipe.gui; import pipe.constants.GUIConstants; import pipe.controllers.SelectionManager; import pipe.controllers.ZoomController; import pipe.views.AbstractPetriNetViewComponent; import pipe.views.PetriNetViewComponent; import uk.ac.imperial.pipe.exceptions.PetriNetComponentException; import uk.ac.imperial.pipe.models.petrinet.*; import uk.ac.imperial.pipe.visitor.component.PetriNetComponentVisitor; import javax.swing.*; import javax.swing.event.MouseInputAdapter; import java.awt.*; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.logging.Level; import java.util.logging.Logger; /** * The main canvas that the {@link pipe.views.PetriNetViewComponent}s appear on * It is a tab in the main applicaiton */ public class PetriNetTab extends JLayeredPane implements Observer, Printable { /** * Class logger */ private static final Logger LOGGER = Logger.getLogger(PetriNetTab.class.getName()); /** * Map of components in the tab with id -> component */ private final Map<String, PetriNetViewComponent> petriNetComponents = new HashMap<>(); /** * Grid displayed on petri net tab */ private final Grid grid = new Grid(); /** * Legacy file for the saving of the underlying Petri net */ @Deprecated public File appFile; /** * Constructor * * Sets no layout manager to acheive an (x,y) layout */ public PetriNetTab() { setLayout(null); setOpaque(true); setDoubleBuffered(true); setAutoscrolls(true); setBackground(GUIConstants.ELEMENT_FILL_COLOUR); setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } /** * * Register the zoom listener to the Petri net tab * * @param zoomController zoom listener */ public void addZoomListener(ZoomController zoomController) { zoomController.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent propertyChangeEvent) { repaint(); } }); } /** * Legacy update method * @param o observable * @param diffObj object to add */ @Override public void update(Observable o, Object diffObj) { if (diffObj instanceof AbstractPetriNetViewComponent) { AbstractPetriNetViewComponent<?> component = (AbstractPetriNetViewComponent<?>) diffObj; addNewPetriNetComponent(component); } } /** * Adds the Petri net component to this canvas * @param component to add to petri net view */ public void addNewPetriNetComponent(AbstractPetriNetViewComponent<?> component) { add(component); component.addToContainer(this); } /** * Add the Petri net component to this canvas * @param component to add */ public void add(AbstractPetriNetViewComponent<?> component) { registerLocationChangeListener(component.getModel()); setLayer(component, DEFAULT_LAYER); super.add(component); petriNetComponents.put(component.getId(), component); updatePreferredSize(); // repaint(); } /** * Update the preferred size of the canvas and grid that is displayed on it */ public void updatePreferredSize() { Component[] components = getComponents(); Dimension d = new Dimension(0, 0); for (Component component : components) { if (component.getClass() == SelectionManager.class) { continue; } Rectangle r = component.getBounds(); int x = r.x + r.width + 20; int y = r.y + r.height + 20; if (x > d.width) { d.width = x; } if (y > d.height) { d.height = y; } } setPreferredSize(d); Container parent = getParent(); if (parent != null) { parent.validate(); } } /** * * Registers a location listener on the Petri net component * * @param component for which a listener will be registered */ private void registerLocationChangeListener(PetriNetComponent component) { PetriNetComponentVisitor changeListener = new ChangeListener(); try { component.accept(changeListener); } catch (PetriNetComponentException e) { LOGGER.log(Level.SEVERE, e.getMessage()); } } /** * Prints the Petri net tab * @param g graphics * @param pageFormat page format * @param pageIndex index * @return printer return code * @throws PrinterException if error in printing */ @Override public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException { if (pageIndex > 0) { return Printable.NO_SUCH_PAGE; } Graphics2D g2D = (Graphics2D) g; g2D.translate(pageFormat.getImageableX(), pageFormat.getImageableY()); g2D.scale(0.5, 0.5); print(g2D); return Printable.PAGE_EXISTS; } /** * Paints the underlying grid on the canvas * @param g graphics */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); if (grid.isEnabled()) { grid.updateSize(this); grid.drawGrid(g); } } /** * Set the cursor type. Options are: * - arrow * - crosshair * - move * @param type cursor type */ //TODO These should be an enum public void setCursorType(String type) { if (type.equals("arrow")) { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } else if (type.equals("crosshair")) { setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } else if (type.equals("move")) { setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); } } /** * Set meta down. Since there is no documentation for this the functionality * has been deprecated and it no longer does anything * @param down flag */ @Deprecated public void setMetaDown(boolean down) { //TODO: DELETE } /** * Updates the canvas boundary when dragging is taking place * @param dragStart start of drag * @param dragEnd end of drag */ public void drag(Point dragStart, Point dragEnd) { if (dragStart == null) { return; } JViewport viewer = (JViewport) getParent(); Point offScreen = viewer.getViewPosition(); if (dragStart.x > dragEnd.x) { offScreen.translate(viewer.getWidth(), 0); } if (dragStart.y > dragEnd.y) { offScreen.translate(0, viewer.getHeight()); } offScreen.translate(dragStart.x - dragEnd.x, dragStart.y - dragEnd.y); Rectangle r = new Rectangle(offScreen.x, offScreen.y, 1, 1); scrollRectToVisible(r); } /** * Remove the component with this id from the canvas * @param id to remove */ public void deletePetriNetComponent(String id) { PetriNetViewComponent component = petriNetComponents.get(id); if (component != null) { component.delete(); remove((Component) component); } validate(); repaint(); } /** * * @return Grid displayed on the canvas */ public Grid getGrid() { return grid; } /** * * @param handler specifies how the canvas should behave to mouse events */ public void setMouseHandler(MouseInputAdapter handler) { addMouseListener(handler); addMouseMotionListener(handler); addMouseWheelListener(handler); } /** * Used to set the bounds of the canvas so that it will expand if components go out of bound */ private class ChangeListener implements PlaceVisitor, TransitionVisitor { /** * Listens to (x,y) changes in components and updates the canvas width * if a place/transition goes out of the current bounds */ private PropertyChangeListener updateListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if (name.equals(Connectable.X_CHANGE_MESSAGE)) { int x = (int) evt.getNewValue(); if (x > getWidth()) { updatePreferredSize(); } } if (name.equals(Connectable.Y_CHANGE_MESSAGE)) { int y = (int) evt.getNewValue(); if (y > getHeight()) { updatePreferredSize(); } } } }; /** * Add the update listener to the place * @param place for which to add listener */ @Override public void visit(Place place) { place.addPropertyChangeListener(updateListener); } /** * Add the update listener to the transition * @param transition for which to add listener */ @Override public void visit(Transition transition) { transition.addPropertyChangeListener(updateListener); } } }