package pipe.views;
import pipe.actions.gui.PipeApplicationModel;
import pipe.constants.GUIConstants;
import pipe.controllers.PetriNetController;
import uk.ac.imperial.pipe.exceptions.PetriNetComponentNotFoundException;
import uk.ac.imperial.pipe.models.petrinet.Arc;
import uk.ac.imperial.pipe.models.petrinet.ArcPoint;
import uk.ac.imperial.pipe.models.petrinet.Connectable;
import uk.ac.imperial.pipe.models.petrinet.Token;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Normal arc view
* @param <S> source model
* @param <T> target model
*/
public class NormalArcView<S extends Connectable, T extends Connectable> extends ArcView<S, T> {
/**
* Class logger
*/
private static final Logger LOGGER = Logger.getLogger(NormalArcView.class.getName());
/**
* Weight labels to display
*/
private final Collection<TextLabel> weightLabel = new LinkedList<>();
/**
* Listens for changes in intermediate points x, y and updates the weights accordingly
*/
private final WeightLabelListener weightListener = new WeightLabelListener();
/**
* Graphical arc head
*/
private ArcHead arcHead = new NormalHead();
/**
* joined if it displays two directional arcs sharing the opposite source and targets
*/
@Deprecated
private boolean joined = false;
/**
* Constructor
* @param model underlying normal arc
* @param controller Petri ent controller for the Petri net that houses the arc
* @param parent view parent
* @param handler mouse event handler
* @param applicationModel PIPE main appliaction model
*/
public NormalArcView(Arc<S, T> model, PetriNetController controller, Container parent, MouseInputAdapter handler,
PipeApplicationModel applicationModel) {
super(model, controller, parent, handler, applicationModel);
addConnectableListener();
addSourceTargetConnectableListener();
for (TextLabel label : weightLabel) {
getParent().add(label);
}
for (ArcPoint arcPoint : model.getArcPoints()) {
arcPoint.addPropertyChangeListener(weightListener);
}
}
/**
* Listens to the source/target changing position
*/
private void addSourceTargetConnectableListener() {
PropertyChangeListener changeListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name.equals(Connectable.X_CHANGE_MESSAGE) || name.equals(Connectable.Y_CHANGE_MESSAGE)) {
updateWeights();
}
}
};
model.getSource().addPropertyChangeListener(changeListener);
model.getTarget().addPropertyChangeListener(changeListener);
}
/**
* Adds a listener to the the arc listening for a change in the arc weights
*/
private void addConnectableListener() {
PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
String name = propertyChangeEvent.getPropertyName();
if (name.equals(Arc.WEIGHT_CHANGE_MESSAGE)) {
updateWeights();
} if (name.equals(Arc.NEW_INTERMEDIATE_POINT_CHANGE_MESSAGE)) {
ArcPoint point = (ArcPoint) propertyChangeEvent.getNewValue();
point.addPropertyChangeListener(weightListener);
updateWeights();
} if (name.equals(Arc.DELETE_INTERMEDIATE_POINT_CHANGE_MESSAGE)) {
ArcPoint point = (ArcPoint) propertyChangeEvent.getOldValue();
point.removePropertyChangeListener(weightListener);
updateWeights();
}
}
};
model.addPropertyChangeListener(listener);
}
/**
* Paints the weight label on the parent
*/
@Override
public void componentSpecificDelete() {
super.componentSpecificDelete();
for (TextLabel label : weightLabel) {
removeLabelFromParentContainer(label);
}
}
/**
* Removes the weight labels from the parent
* @param label
*/
private void removeLabelFromParentContainer(TextLabel label) {
getParent().remove(label);
}
/**
* When added to the container it updates the paths and weights
* @param container to add itself to
*/
@Override
public void addToContainer(Container container) {
updatePath();
updateWeights();
}
/**
* Creates and paints the weights in the center of the arc
*/
private void updateWeights() {
removeCurrentWeights();
createWeightLabels();
setWeightLabelPosition();
addWeightLabelsToContainer(getParent());
}
/**
* Removes the weights from the arc
*/
private void removeCurrentWeights() {
for (TextLabel name : weightLabel) {
removeLabelFromParentContainer(name);
}
weightLabel.clear();
}
/**
* Creates token weight labels
*/
private void createWeightLabels() {
Map<String, String> weights = model.getTokenWeights();
for (Map.Entry<String, String> entry : weights.entrySet()) {
String weight = entry.getValue();
String tokenId = entry.getKey();
TextLabel label = new TextLabel(weight);
try {
Token token = petriNetController.getToken(tokenId);
label.setColor(token.getColor());
} catch (PetriNetComponentNotFoundException e) {
LOGGER.log(Level.SEVERE, e.getMessage());
label.setColor(Color.BLACK);
}
label.updateSize();
weightLabel.add(label);
}
}
/**
* Set the position of the weight labels to the middle of the arc
*/
protected void setWeightLabelPosition() {
int originalX = (int) arcPath.midPoint.x;
int originalY = (int) arcPath.midPoint.y - 10;
int x = originalX;
int y = originalY;
int yCount = 0;
for (TextLabel label : weightLabel) {
if (yCount >= 4) {
y = originalY;
x += 17;
yCount = 0;
}
label.setPosition(x + label.getWidth() / 2 - 4, y);
y += 10;
yCount++;
}
}
/**
* Add weight labels to the container
* @param container
*/
private void addWeightLabelsToContainer(Container container) {
for (TextLabel label : weightLabel) {
container.add(label);
}
}
/**
* Paints the arc
* @param g graphics
*/
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
AffineTransform reset = g2.getTransform();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.translate(getComponentDrawOffset() + ZOOM_GROW - arcPath.getBounds().getX(),
getComponentDrawOffset() + ZOOM_GROW - arcPath.getBounds().getY());
if (isSelected() && !ignoreSelection) {
g2.setPaint(GUIConstants.SELECTION_LINE_COLOUR);
} else {
g2.setPaint(GUIConstants.ELEMENT_LINE_COLOUR);
}
if (joined) {
g2.translate(arcPath.getPoint(0).getX(), arcPath.getPoint(0).getY());
g2.rotate(arcPath.getStartAngle() + Math.PI);
g2.setTransform(reset);
}
g2.setStroke(new BasicStroke(1f));
g2.draw(arcPath);
g2.translate(arcPath.getPoint(arcPath.getEndIndex()).getX(), arcPath.getPoint(arcPath.getEndIndex()).getY());
g2.rotate(model.getEndAngle());
g2.setColor(java.awt.Color.WHITE);
if (isSelected() && !ignoreSelection) {
g2.setPaint(GUIConstants.SELECTION_LINE_COLOUR);
} else {
g2.setPaint(GUIConstants.ELEMENT_LINE_COLOUR);
}
arcHead.draw(g2);
g2.transform(reset);
}
/**
* Weight label listener for updating the arc weight label location if arc points change
*/
private class WeightLabelListener implements PropertyChangeListener {
/**
* If the event fired is an arc point (x,y) location change then the
* weights label location is recalculated
* @param evt
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name.equals(ArcPoint.UPDATE_LOCATION_CHANGE_MESSAGE)) {
updateWeights();
}
}
}
}