package pipe.views; import pipe.constants.GUIConstants; import pipe.controllers.PetriNetController; import uk.ac.imperial.pipe.exceptions.PetriNetComponentNotFoundException; import uk.ac.imperial.pipe.models.petrinet.Place; import uk.ac.imperial.pipe.models.petrinet.Token; import javax.swing.event.MouseInputAdapter; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * Graphical representation of a Place */ public class PlaceView extends ConnectableView<Place> { /** * Class logger */ private static final Logger LOGGER = Logger.getLogger(PlaceView.class.getName()); /** * Constructor * @param model underlying model * @param parent parent of the view * @param controller Petri net controller of the Petri net the model is housed in * @param placeHandler mouse input handler for describing how this view responds to mouse events */ public PlaceView(Place model, Container parent, PetriNetController controller, MouseInputAdapter placeHandler) { super(model.getId(), model, controller, controller.getPlaceController(model), parent, new Ellipse2D.Double(-model.getWidth()/2, -model.getHeight()/2, model.getWidth(), model.getHeight())); setChangeListener(); setMouseListener(placeHandler); Rectangle bounds = shape.getBounds(); Rectangle newBounds = new Rectangle((int)(model.getCentre().getX() + bounds.getX()), (int)(model.getCentre().getY() + bounds.getY()), (int) bounds.getWidth() + getComponentDrawOffset(), (int)bounds.getHeight() + getComponentDrawOffset()); setBounds(newBounds); } /** * * Sets the view mouse listeners * * @param placeHandler mouse input handler for describing how this view responds to mouse events */ private void setMouseListener(MouseInputAdapter placeHandler) { this.addMouseListener(placeHandler); this.addMouseWheelListener(placeHandler); this.addMouseMotionListener(placeHandler); } /** * Listens for changes in the model * All changes cause a repaint */ private void setChangeListener() { model.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent propertyChangeEvent) { repaint(); } }); } /** * Paints the Place component taking into account the n q12[umber of tokens from * the storeCurrentMarking * * @param g The PositionGraphics object onto which the Place is drawn. */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g.create(); Rectangle bounds = shape.getBounds(); g2.translate(bounds.getWidth()/2, bounds.getHeight()/2); if (hasCapacity()) { g2.setStroke(new BasicStroke(2.0f)); setToolTipText("k = " + model.getCapacity()); } else { g2.setStroke(new BasicStroke(1.0f)); setToolTipText("k = \u221E"); } g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (isSelected() && !ignoreSelection) { g2.setColor(GUIConstants.SELECTION_FILL_COLOUR); } else { g2.setColor(GUIConstants.ELEMENT_FILL_COLOUR); } g2.fill(shape); if (isSelected() && !ignoreSelection) { g2.setPaint(GUIConstants.SELECTION_LINE_COLOUR); } else { g2.setPaint(GUIConstants.ELEMENT_LINE_COLOUR); } g2.draw(shape); g2.setStroke(new BasicStroke(1.0f)); // Paints border round a tagged place - paint component is called after any action on the place, so this bit // of code doesn't have to be called specially if (this.isTagged()) { AffineTransform oldTransform = g2.getTransform(); AffineTransform scaleTransform = new AffineTransform(); scaleTransform.setToScale(1.2, 1.2); g2.transform(scaleTransform); g2.translate(-2, -2); g2.fill(shape); g2.translate(2, 2); g2.setTransform(oldTransform); } g2 = (Graphics2D) g.create(); paintTokens(g2); g2.dispose(); } /** * Displays tokens in the Place */ private void paintTokens(Graphics2D g2) { int totalMarking = model.getNumberOfTokensStored(); boolean displayTextualNumber = totalMarking > 5; if (displayTextualNumber) { displayTextualTokens(g2); } else { displayOvalTokens(g2); } } /** * Displays each token in the Place as an oval * * If the token does not exist in the petri net the color is displayed * as black * * * @param g2 graphics */ private void displayOvalTokens(Graphics2D g2) { int offset = 0; Map<String, Integer> tokenCounts = model.getTokenCounts(); for (Map.Entry<String, Integer> entry : tokenCounts.entrySet()) { String tokenId = entry.getKey(); Integer count = entry.getValue(); Insets insets = getInsets(); Token token = null; try { token = petriNetController.getToken(tokenId); paintOvalTokens(g2, insets, token.getColor(), count, offset); } catch (PetriNetComponentNotFoundException e) { LOGGER.log(Level.SEVERE, e.getMessage()); paintOvalTokens(g2, insets, Color.BLACK, count, offset); } offset += count; } } /** * Paints tokens as ovals on the place * Can only paint five tokens so tokenNumber represents which number the count is * starting at * * @param g2 graphics * @param insets insets * @param color colour of oval * @param count number of token ovals to paint * @param tokenNumber token number */ private void paintOvalTokens(Graphics2D g2, Insets insets, Color color, int count, int tokenNumber) { int x = insets.left; int y = insets.top; g2.setColor(color); int WIDTH = 4; int HEIGHT = 4; int position = tokenNumber; for (int i = 0; i < count; i++) { switch (position) { case 4: g2.drawOval(x + 6, y + 6, WIDTH, HEIGHT); g2.fillOval(x + 6, y + 6, WIDTH, HEIGHT); break; case 3: g2.drawOval(x + 18, y + 20, WIDTH, HEIGHT); g2.fillOval(x + 18, y + 20, WIDTH, HEIGHT); break; case 2: g2.drawOval(x + 6, y + 20, WIDTH, HEIGHT); g2.fillOval(x + 6, y + 20, WIDTH, HEIGHT); break; case 1: g2.drawOval(x + 18, y + 6, WIDTH, HEIGHT); g2.fillOval(x + 18, y + 6, WIDTH, HEIGHT); break; case 0: g2.drawOval(x + 12, y + 13, WIDTH, HEIGHT); g2.fillOval(x + 12, y + 13, WIDTH, HEIGHT); break; default: break; } position++; } } /** * Display each token in the place as a number textually * * If the token does not exist in the petri net the color is displayed * as black * * @param g2 graphics */ private void displayTextualTokens(Graphics2D g2) { int offset = 0; Map<String, Integer> tokenCounts = model.getTokenCounts(); for (Map.Entry<String, Integer> entry : tokenCounts.entrySet()) { String tokenId = entry.getKey(); Integer count = entry.getValue(); Insets insets = getInsets(); try { Token token = petriNetController.getToken(tokenId); paintTextualTokens(g2, insets, token.getColor(), count, offset); } catch (PetriNetComponentNotFoundException e) { LOGGER.log(Level.SEVERE, e.getMessage()); paintTextualTokens(g2, insets, Color.BLACK, count, offset); } offset += 10; } } /** * Paints tokens as a string representation of their number in the place * * @param g2 graphics * @param insets insets * @param color color of token * @param count number of tokens to represent * @param offset offset of textual representation relative to the place */ private void paintTextualTokens(Graphics2D g2, Insets insets, Color color, int count, int offset) { int x = insets.left; int y = insets.top; g2.setColor(color); if (count > 999) { g2.drawString(String.valueOf(count), x, y + 10 + offset); } else if (count > 99) { g2.drawString(String.valueOf(count), x + 3, y + 10 + offset); } else if (count > 9) { g2.drawString(String.valueOf(count), x + 7, y + 10 + offset); } else if (count != 0) { g2.drawString(String.valueOf(count), x + 12, y + 10 + offset); } } /** * * @return true if the underlying model has a capacity */ private boolean hasCapacity() { return model.getCapacity() > 0; } /** * * @return if the place is tagged */ private boolean isTagged() { return false; } /** * Adds the place id label to the container * @param container to add itself to */ @Override public void addToContainer(Container container) { addLabelToContainer(container); } }