java/src/jmri/jmrit/display/layoutEditor/LayoutTurnoutView.java
package jmri.jmrit.display.layoutEditor;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.geom.*;
import static java.lang.Float.POSITIVE_INFINITY;
import java.util.*;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.swing.*;
import jmri.*;
import jmri.jmrit.display.layoutEditor.LayoutTurnout.Geometry;
import jmri.jmrit.display.layoutEditor.LayoutTurnout.LinkType;
import jmri.jmrit.display.layoutEditor.LayoutTurnout.TurnoutType;
import jmri.jmrit.display.layoutEditor.blockRoutingTable.LayoutBlockRouteTableAction;
import jmri.util.MathUtil;
import jmri.util.swing.JmriJOptionPane;
import jmri.util.swing.JmriMouseEvent;
/**
* MVC View component for the LayoutTurnout class.
*
* @author Bob Jacobsen Copyright (c) 2020
*
*/
public class LayoutTurnoutView extends LayoutTrackView {
public LayoutTurnoutView(@Nonnull LayoutTurnout turnout,
@Nonnull Point2D c, double rot,
@Nonnull LayoutEditor layoutEditor) {
this(turnout, c, rot, 1.0, 1.0, layoutEditor);
}
/**
* Constructor method.
*
* @param turnout the layout turnout to create the view for.
* @param c where to put it
* @param rot for display
* @param xFactor for display
* @param yFactor for display
* @param layoutEditor what layout editor panel to put it in
*/
public LayoutTurnoutView(@Nonnull LayoutTurnout turnout,
@Nonnull Point2D c, double rot,
double xFactor, double yFactor,
@Nonnull LayoutEditor layoutEditor) {
super(turnout, c, layoutEditor);
this.turnout = turnout;
setIdent(turnout.getName());
int version = turnout.getVersion();
// adjust initial coordinates
if (turnout.getTurnoutType() == TurnoutType.LH_TURNOUT) {
dispB = new Point2D.Double(layoutEditor.getTurnoutBX(), 0.0);
dispA = new Point2D.Double(layoutEditor.getTurnoutCX(), -layoutEditor.getTurnoutWid());
} else if (turnout.getTurnoutType() == TurnoutType.RH_TURNOUT) {
dispB = new Point2D.Double(layoutEditor.getTurnoutBX(), 0.0);
dispA = new Point2D.Double(layoutEditor.getTurnoutCX(), layoutEditor.getTurnoutWid());
} else if (turnout.getTurnoutType() == TurnoutType.WYE_TURNOUT) {
dispB = new Point2D.Double(layoutEditor.getTurnoutBX(), 0.5 * layoutEditor.getTurnoutWid());
dispA = new Point2D.Double(layoutEditor.getTurnoutBX(), -0.5 * layoutEditor.getTurnoutWid());
} else if (turnout.getTurnoutType() == TurnoutType.DOUBLE_XOVER) {
if (version == 2) {
super.setCoordsCenter(new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid()));
pointB = new Point2D.Double(layoutEditor.getXOverLong() * 2, 0);
pointC = new Point2D.Double(layoutEditor.getXOverLong() * 2, (layoutEditor.getXOverHWid() * 2));
pointD = new Point2D.Double(0, (layoutEditor.getXOverHWid() * 2));
super.setCoordsCenter(c);
} else {
dispB = new Point2D.Double(layoutEditor.getXOverLong(), -layoutEditor.getXOverHWid());
dispA = new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid());
}
} else if (turnout.getTurnoutType() == TurnoutType.RH_XOVER) {
if (version == 2) {
super.setCoordsCenter(new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid()));
pointB = new Point2D.Double((layoutEditor.getXOverShort() + layoutEditor.getXOverLong()), 0);
pointC = new Point2D.Double(layoutEditor.getXOverLong() * 2, (layoutEditor.getXOverHWid() * 2));
pointD = new Point2D.Double((getCoordsCenter().getX() - layoutEditor.getXOverShort()), (layoutEditor.getXOverHWid() * 2));
super.setCoordsCenter(c);
} else {
dispB = new Point2D.Double(layoutEditor.getXOverShort(), -layoutEditor.getXOverHWid());
dispA = new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid());
}
} else if (turnout.getTurnoutType() == TurnoutType.LH_XOVER) {
if (version == 2) {
super.setCoordsCenter(new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid()));
pointA = new Point2D.Double((getCoordsCenter().getX() - layoutEditor.getXOverShort()), 0);
pointB = new Point2D.Double((layoutEditor.getXOverLong() * 2), 0);
pointC = new Point2D.Double(layoutEditor.getXOverLong() + layoutEditor.getXOverShort(), (layoutEditor.getXOverHWid() * 2));
pointD = new Point2D.Double(0, (layoutEditor.getXOverHWid() * 2));
super.setCoordsCenter(c);
} else {
dispB = new Point2D.Double(layoutEditor.getXOverLong(), -layoutEditor.getXOverHWid());
dispA = new Point2D.Double(layoutEditor.getXOverShort(), layoutEditor.getXOverHWid());
}
}
rotateCoords(rot);
// adjust size of new turnout
Point2D pt = new Point2D.Double(Math.round(dispB.getX() * xFactor),
Math.round(dispB.getY() * yFactor));
dispB = pt;
pt = new Point2D.Double(Math.round(dispA.getX() * xFactor),
Math.round(dispA.getY() * yFactor));
dispA = pt;
editor = new jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTurnoutEditor(layoutEditor);
}
/**
* Returns true if this is a turnout (not a crossover or slip)
*
* @param type the turnout type
* @return boolean true if this is a turnout
*/
public static boolean isTurnoutTypeTurnout(TurnoutType type) {
return LayoutTurnout.isTurnoutTypeTurnout(type);
}
/**
* Returns true if this is a turnout (not a crossover or slip)
*
* @return boolean true if this is a turnout
*/
public boolean isTurnoutTypeTurnout() {
return turnout.isTurnoutTypeTurnout();
}
/**
* Returns true if this is a crossover
*
* @param type the turnout type
* @return boolean true if this is a crossover
*/
public static boolean isTurnoutTypeXover(TurnoutType type) {
return LayoutTurnout.isTurnoutTypeXover(type);
}
/**
* Returns true if this is a crossover
*
* @return boolean true if this is a crossover
*/
public boolean isTurnoutTypeXover() {
return turnout.isTurnoutTypeXover();
}
/**
* Returns true if this is a slip
*
* @param type the turnout type
* @return boolean true if this is a slip
*/
public static boolean isTurnoutTypeSlip(TurnoutType type) {
return LayoutTurnout.isTurnoutTypeSlip(type);
}
/**
* Returns true if this is a slip
*
* @return boolean true if this is a slip
*/
public boolean isTurnoutTypeSlip() {
return turnout.isTurnoutTypeSlip();
}
/**
* Returns true if this has a single-track entrance end. (turnout or wye)
*
* @param type the turnout type
* @return boolean true if single track entrance
*/
public static boolean hasEnteringSingleTrack(TurnoutType type) {
return LayoutTurnout.hasEnteringSingleTrack(type);
}
/**
* Returns true if this has a single-track entrance end. (turnout or wye)
*
* @return boolean true if single track entrance
*/
public boolean hasEnteringSingleTrack() {
return LayoutTurnout.hasEnteringSingleTrack(getTurnoutType());
}
/**
* Returns true if this has double track on the entrance end (crossover or
* slip)
*
* @param type the turnout type
* @return boolean true if double track entrance
*/
public static boolean hasEnteringDoubleTrack(TurnoutType type) {
return LayoutTurnout.hasEnteringDoubleTrack(type);
}
/**
* Returns true if this has double track on the entrance end (crossover or
* slip)
*
* @return boolean true if double track entrance
*/
public boolean hasEnteringDoubleTrack() {
return turnout.hasEnteringDoubleTrack();
}
// operational instance variables (not saved between sessions)
public static final int UNKNOWN = Turnout.UNKNOWN;
public static final int INCONSISTENT = Turnout.INCONSISTENT;
public static final int STATE_AC = 0x02;
public static final int STATE_BD = 0x04;
public static final int STATE_AD = 0x06;
public static final int STATE_BC = 0x08;
// program default turnout size parameters
public static final double turnoutBXDefault = 20.0; // RH, LH, WYE
public static final double turnoutCXDefault = 20.0;
public static final double turnoutWidDefault = 10.0;
public static final double xOverLongDefault = 30.0; // DOUBLE_XOVER, RH_XOVER, LH_XOVER
public static final double xOverHWidDefault = 10.0;
public static final double xOverShortDefault = 10.0;
// operational instance variables (not saved between sessions)
protected NamedBeanHandle<Turnout> namedTurnout = null;
// Second turnout is used to either throw a second turnout in a cross over or if one turnout address is used to throw two physical ones
protected NamedBeanHandle<Turnout> secondNamedTurnout = null;
// default is package protected
protected NamedBeanHandle<LayoutBlock> namedLayoutBlockA = null;
protected NamedBeanHandle<LayoutBlock> namedLayoutBlockB = null; // Xover - second block, if there is one
protected NamedBeanHandle<LayoutBlock> namedLayoutBlockC = null; // Xover - third block, if there is one
protected NamedBeanHandle<LayoutBlock> namedLayoutBlockD = null; // Xover - forth block, if there is one
protected NamedBeanHandle<SignalHead> signalA1HeadNamed = null; // signal 1 (continuing) (throat for RH, LH, WYE)
protected NamedBeanHandle<SignalHead> signalA2HeadNamed = null; // signal 2 (diverging) (throat for RH, LH, WYE)
protected NamedBeanHandle<SignalHead> signalA3HeadNamed = null; // signal 3 (second diverging) (3-way turnouts only)
protected NamedBeanHandle<SignalHead> signalB1HeadNamed = null; // continuing (RH, LH, WYE) signal 1 (double crossover)
protected NamedBeanHandle<SignalHead> signalB2HeadNamed = null; // LH_Xover and double crossover only
protected NamedBeanHandle<SignalHead> signalC1HeadNamed = null; // diverging (RH, LH, WYE) signal 1 (double crossover)
protected NamedBeanHandle<SignalHead> signalC2HeadNamed = null; // RH_Xover and double crossover only
protected NamedBeanHandle<SignalHead> signalD1HeadNamed = null; // single or double crossover only
protected NamedBeanHandle<SignalHead> signalD2HeadNamed = null; // LH_Xover and double crossover only
public Point2D dispB = new Point2D.Double(20.0, 0.0);
public Point2D dispA = new Point2D.Double(20.0, 10.0);
public Point2D pointA = new Point2D.Double(0, 0);
public Point2D pointB = new Point2D.Double(40, 0);
public Point2D pointC = new Point2D.Double(60, 20);
public Point2D pointD = new Point2D.Double(20, 20);
private int version = 1;
private final boolean useBlockSpeed = false;
// temporary reference to the Editor that will eventually be part of View
protected jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTurnoutEditor editor;
final private LayoutTurnout turnout;
public final LayoutTurnout getLayoutTurnout() {
return turnout;
} // getTurnout() gets the real Turnout in the LayoutTurnout
/**
* {@inheritDoc}
*/
// this should only be used for debugging...
@Override
@Nonnull
public String toString() {
return "LayoutTurnout " + getName();
}
//
// Accessor methods
//
public int getVersion() {
return version;
}
public void setVersion(int v) {
version = v;
}
public boolean useBlockSpeed() {
return useBlockSpeed;
}
// @CheckForNull - can this be null? or ""?
public String getTurnoutName() {
return turnout.getTurnoutName();
}
// @CheckForNull - can this be null? or ""?
public String getSecondTurnoutName() {
return turnout.getSecondTurnoutName();
}
@Nonnull
public String getBlockName() {
return turnout.getBlockName();
}
@Nonnull
public String getBlockBName() {
return turnout.getBlockBName();
}
@Nonnull
public String getBlockCName() {
return turnout.getBlockCName();
}
@Nonnull
public String getBlockDName() {
return turnout.getBlockDName();
}
@CheckForNull
public SignalHead getSignalHead(Geometry loc) {
return turnout.getSignalHead(loc);
}
@CheckForNull
public SignalHead getSignalA1() {
return turnout.getSignalA1();
}
@Nonnull
public String getSignalA1Name() {
return turnout.getSignalA1Name();
}
public void setSignalA1Name(@CheckForNull String signalHead) {
turnout.setSignalA1Name(signalHead);
}
@CheckForNull
public SignalHead getSignalA2() {
return turnout.getSignalA2();
}
@Nonnull
public String getSignalA2Name() {
return turnout.getSignalA2Name();
}
public void setSignalA2Name(@CheckForNull String signalHead) {
turnout.setSignalA2Name(signalHead);
}
@CheckForNull
public SignalHead getSignalA3() {
return turnout.getSignalA3();
}
@Nonnull
public String getSignalA3Name() {
return turnout.getSignalA3Name();
}
public void setSignalA3Name(@CheckForNull String signalHead) {
turnout.setSignalA3Name(signalHead);
}
@CheckForNull
public SignalHead getSignalB1() {
return turnout.getSignalB1();
}
@Nonnull
public String getSignalB1Name() {
return turnout.getSignalB1Name();
}
public void setSignalB1Name(@CheckForNull String signalHead) {
turnout.setSignalB1Name(signalHead);
}
@CheckForNull
public SignalHead getSignalB2() {
return turnout.getSignalB2();
}
@Nonnull
public String getSignalB2Name() {
return turnout.getSignalB2Name();
}
public void setSignalB2Name(@CheckForNull String signalHead) {
turnout.setSignalB2Name(signalHead);
}
@CheckForNull
public SignalHead getSignalC1() {
return turnout.getSignalC1();
}
@Nonnull
public String getSignalC1Name() {
return turnout.getSignalC1Name();
}
public void setSignalC1Name(@CheckForNull String signalHead) {
turnout.setSignalC1Name(signalHead);
}
@CheckForNull
public SignalHead getSignalC2() {
return turnout.getSignalC2();
}
@Nonnull
public String getSignalC2Name() {
return turnout.getSignalC2Name();
}
public void setSignalC2Name(@CheckForNull String signalHead) {
turnout.setSignalC2Name(signalHead);
}
@CheckForNull
public SignalHead getSignalD1() {
return turnout.getSignalD1();
}
@Nonnull
public String getSignalD1Name() {
return turnout.getSignalD1Name();
}
public void setSignalD1Name(@CheckForNull String signalHead) {
turnout.setSignalD1Name(signalHead);
}
@CheckForNull
public SignalHead getSignalD2() {
return turnout.getSignalD2();
}
@Nonnull
public String getSignalD2Name() {
return turnout.getSignalD2Name();
}
public void setSignalD2Name(@CheckForNull String signalHead) {
turnout.setSignalD2Name(signalHead);
}
public void removeBeanReference(@CheckForNull jmri.NamedBean nb) {
turnout.removeBeanReference(nb);
}
/**
* {@inheritDoc}
*/
@Override
public boolean canRemove() {
return turnout.canRemove();
}
/**
* Build a list of sensors, signal heads, and signal masts attached to a
* turnout point.
*
* @param pointName Specify the point (A-D) or all (All) points.
* @return a list of bean reference names.
*/
@Nonnull
public ArrayList<String> getBeanReferences(String pointName) {
throw new IllegalArgumentException("should be called on LayoutTurnout");
}
@Nonnull
public String getSignalAMastName() {
return turnout.getSignalAMastName();
}
@CheckForNull
public SignalMast getSignalAMast() {
return turnout.getSignalAMast();
}
public void setSignalAMast(@CheckForNull String signalMast) {
turnout.setSignalAMast(signalMast);
}
@Nonnull
public String getSignalBMastName() {
return turnout.getSignalBMastName();
}
@CheckForNull
public SignalMast getSignalBMast() {
return turnout.getSignalBMast();
}
public void setSignalBMast(@CheckForNull String signalMast) {
turnout.setSignalBMast(signalMast);
}
@Nonnull
public String getSignalCMastName() {
return turnout.getSignalCMastName();
}
@CheckForNull
public SignalMast getSignalCMast() {
return turnout.getSignalCMast();
}
public void setSignalCMast(@CheckForNull String signalMast) {
turnout.setSignalCMast(signalMast);
}
@Nonnull
public String getSignalDMastName() {
return turnout.getSignalDMastName();
}
@CheckForNull
public SignalMast getSignalDMast() {
return turnout.getSignalDMast();
}
public void setSignalDMast(@CheckForNull String signalMast) {
turnout.setSignalDMast(signalMast);
}
@Nonnull
public String getSensorAName() {
return turnout.getSensorAName();
}
@CheckForNull
public Sensor getSensorA() {
return turnout.getSensorA();
}
public void setSensorA(@CheckForNull String sensorName) {
turnout.setSensorA(sensorName);
}
@Nonnull
public String getSensorBName() {
return turnout.getSensorBName();
}
@CheckForNull
public Sensor getSensorB() {
return turnout.getSensorB();
}
public void setSensorB(@CheckForNull String sensorName) {
turnout.setSensorB(sensorName);
}
@Nonnull
public String getSensorCName() {
return turnout.getSensorCName();
}
@CheckForNull
public Sensor getSensorC() {
return turnout.getSensorC();
}
public void setSensorC(@CheckForNull String sensorName) {
turnout.setSensorC(sensorName);
}
@Nonnull
public String getSensorDName() {
return turnout.getSensorDName();
}
@CheckForNull
public Sensor getSensorD() {
return turnout.getSensorD();
}
public void setSensorD(@CheckForNull String sensorName) {
turnout.setSensorD(sensorName);
}
public String getLinkedTurnoutName() {
return turnout.getLinkedTurnoutName();
}
public void setLinkedTurnoutName(@Nonnull String s) {
turnout.setSensorD(s);
} // Could be done with changing over to a NamedBeanHandle
public LinkType getLinkType() {
return turnout.getLinkType();
}
public void setLinkType(LinkType ltype) {
turnout.setLinkType(ltype);
}
public TurnoutType getTurnoutType() {
return turnout.getTurnoutType();
}
public LayoutTrack getConnectA() {
return turnout.getConnectA();
}
public LayoutTrack getConnectB() {
return turnout.getConnectB();
}
public LayoutTrack getConnectC() {
return turnout.getConnectC();
}
public LayoutTrack getConnectD() {
return turnout.getConnectD();
}
/**
* @return null if no turnout set // temporary? Might want to run all calls
* through this class; but this is getModel equiv
*/
// @CheckForNull temporary
public Turnout getTurnout() {
return turnout.getTurnout();
}
public int getContinuingSense() {
return turnout.getContinuingSense();
}
/**
*
* @return true is the continuingSense matches the known state
*/
public boolean isInContinuingSenseState() {
return turnout.isInContinuingSenseState();
}
public void setTurnout(@Nonnull String tName) {
turnout.setTurnout(tName);
}
// @CheckForNull - need to have a better way to handle null case
public Turnout getSecondTurnout() {
return turnout.getSecondTurnout();
}
public void setSecondTurnout(@Nonnull String tName) {
turnout.setSecondTurnout(tName);
}
public void setSecondTurnoutInverted(boolean inverted) {
turnout.setSecondTurnoutInverted(inverted);
}
public void setContinuingSense(int sense) {
turnout.setContinuingSense(sense);
}
public void setDisabled(boolean state) {
turnout.setDisabled(state);
if (layoutEditor != null) {
layoutEditor.redrawPanel();
}
}
public boolean isDisabled() {
return turnout.isDisabled();
}
public void setDisableWhenOccupied(boolean state) {
turnout.setDisableWhenOccupied(state);
if (layoutEditor != null) {
layoutEditor.redrawPanel();
}
}
public boolean isDisabledWhenOccupied() {
return turnout.isDisabledWhenOccupied();
}
/**
* {@inheritDoc}
*/
@Override
@CheckForNull
public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
return turnout.getConnection(connectionType);
}
/**
* {@inheritDoc}
*/
@Override
public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException {
turnout.setConnection(connectionType, o, type);
}
public void setConnectA(@CheckForNull LayoutTrack o, HitPointType type) {
turnout.setConnectA(o, type);
}
public void setConnectB(@CheckForNull LayoutTrack o, HitPointType type) {
turnout.setConnectB(o, type);
}
public void setConnectC(@CheckForNull LayoutTrack o, HitPointType type) {
turnout.setConnectC(o, type);
}
public void setConnectD(@CheckForNull LayoutTrack o, HitPointType type) {
turnout.setConnectD(o, type);
}
// @CheckForNull - temporary while we work on centralized protection
public LayoutBlock getLayoutBlock() {
return turnout.getLayoutBlock();
}
// @CheckForNull - temporary while we work on centralized protection
public LayoutBlock getLayoutBlockB() {
return turnout.getLayoutBlockB();
}
// @CheckForNull - temporary while we work on centralized protection
public LayoutBlock getLayoutBlockC() {
return turnout.getLayoutBlockC();
}
// @CheckForNull - temporary while we work on centralized protection
public LayoutBlock getLayoutBlockD() {
return turnout.getLayoutBlockD();
}
@Nonnull
public Point2D getCoordsA() {
if (isTurnoutTypeXover()) {
if (version == 2) {
return pointA;
}
return MathUtil.subtract(getCoordsCenter(), dispA);
} else if (getTurnoutType() == TurnoutType.WYE_TURNOUT) {
return MathUtil.subtract(getCoordsCenter(), MathUtil.midPoint(dispB, dispA));
} else {
return MathUtil.subtract(getCoordsCenter(), dispB);
}
}
@Nonnull
public Point2D getCoordsB() {
if ((version == 2) && isTurnoutTypeXover()) {
return pointB;
}
return MathUtil.add(getCoordsCenter(), dispB);
}
@Nonnull
public Point2D getCoordsC() {
if ((version == 2) && isTurnoutTypeXover()) {
return pointC;
}
return MathUtil.add(getCoordsCenter(), dispA);
}
@Nonnull
public Point2D getCoordsD() {
if ((version == 2) && isTurnoutTypeXover()) {
return pointD;
}
// only allowed for single and double crossovers
return MathUtil.subtract(getCoordsCenter(), dispB);
}
/**
* {@inheritDoc}
*/
@Override
@Nonnull
public Point2D getCoordsForConnectionType(HitPointType connectionType) {
Point2D result = getCoordsCenter();
switch (connectionType) {
case TURNOUT_CENTER:
break;
case TURNOUT_A:
result = getCoordsA();
break;
case TURNOUT_B:
result = getCoordsB();
break;
case TURNOUT_C:
result = getCoordsC();
break;
case TURNOUT_D:
result = getCoordsD();
break;
default:
log.error("{}.getCoordsForConnectionType({}); Invalid Connection Type",
getName(), connectionType); // NOI18N
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
@Nonnull
public Rectangle2D getBounds() {
Rectangle2D result;
Point2D pointA = getCoordsA();
result = new Rectangle2D.Double(pointA.getX(), pointA.getY(), 0, 0);
result.add(getCoordsB());
result.add(getCoordsC());
if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
result.add(getCoordsD());
}
return result;
}
// updates connectivity for blocks assigned to this turnout and connected track segments
public void updateBlockInfo() {
turnout.updateBlockInfo();
}
/**
* Set default size parameters to correspond to this turnout's size.
* <p>
* note: only protected so LayoutTurnoutTest can call it
*/
protected void setUpDefaultSize() {
// remove the overall scale factor
double bX = dispB.getX() / layoutEditor.gContext.getXScale();
double bY = dispB.getY() / layoutEditor.gContext.getYScale();
double cX = dispA.getX() / layoutEditor.gContext.getXScale();
double cY = dispA.getY() / layoutEditor.gContext.getYScale();
// calculate default parameters according to type of turnout
double lenB = Math.hypot(bX, bY);
double lenC = Math.hypot(cX, cY);
double distBC = Math.hypot(bX - cX, bY - cY);
if ((getTurnoutType() == TurnoutType.LH_TURNOUT)
|| (getTurnoutType() == TurnoutType.RH_TURNOUT)) {
layoutEditor.setTurnoutBX(Math.round(lenB + 0.1));
double xc = ((bX * cX) + (bY * cY)) / lenB;
layoutEditor.setTurnoutCX(Math.round(xc + 0.1));
layoutEditor.setTurnoutWid(Math.round(Math.sqrt((lenC * lenC) - (xc * xc)) + 0.1));
} else if (getTurnoutType() == TurnoutType.WYE_TURNOUT) {
double xx = Math.sqrt((lenB * lenB) - (0.25 * (distBC * distBC)));
layoutEditor.setTurnoutBX(Math.round(xx + 0.1));
layoutEditor.setTurnoutCX(Math.round(xx + 0.1));
layoutEditor.setTurnoutWid(Math.round(distBC + 0.1));
} else {
if (version == 2) {
double aX = pointA.getX() / layoutEditor.gContext.getXScale();
double aY = pointA.getY() / layoutEditor.gContext.getYScale();
bX = pointB.getX() / layoutEditor.gContext.getXScale();
bY = pointB.getY() / layoutEditor.gContext.getYScale();
cX = pointC.getX() / layoutEditor.gContext.getXScale();
cY = pointC.getY() / layoutEditor.gContext.getYScale();
double lenAB = Math.hypot(bX - aX, bY - aY);
if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) {
double lenBC = Math.hypot(bX - cX, bY - cY);
layoutEditor.setXOverLong(Math.round(lenAB / 2)); // set to half to be backwardly compatible
layoutEditor.setXOverHWid(Math.round(lenBC / 2));
layoutEditor.setXOverShort(Math.round((0.5 * lenAB) / 2));
} else if (getTurnoutType() == TurnoutType.RH_XOVER) {
lenAB = lenAB / 3;
layoutEditor.setXOverShort(Math.round(lenAB));
layoutEditor.setXOverLong(Math.round(lenAB * 2));
double opp = (aY - bY);
double ang = Math.asin(opp / (lenAB * 3));
opp = Math.sin(ang) * lenAB;
bY = bY + opp;
double adj = Math.cos(ang) * lenAB;
bX = bX + adj;
double lenBC = Math.hypot(bX - cX, bY - cY);
layoutEditor.setXOverHWid(Math.round(lenBC / 2));
} else if (getTurnoutType() == TurnoutType.LH_XOVER) {
double dY = pointD.getY() / layoutEditor.gContext.getYScale();
lenAB = lenAB / 3;
layoutEditor.setXOverShort(Math.round(lenAB));
layoutEditor.setXOverLong(Math.round(lenAB * 2));
double opp = (dY - cY);
double ang = Math.asin(opp / (lenAB * 3)); // Length of AB should be the same as CD
opp = Math.sin(ang) * lenAB;
cY = cY + opp;
double adj = Math.cos(ang) * lenAB;
cX = cX + adj;
double lenBC = Math.hypot(bX - cX, bY - cY);
layoutEditor.setXOverHWid(Math.round(lenBC / 2));
}
} else if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) {
double lng = Math.sqrt((lenB * lenB) - (0.25 * (distBC * distBC)));
layoutEditor.setXOverLong(Math.round(lng + 0.1));
layoutEditor.setXOverHWid(Math.round((0.5 * distBC) + 0.1));
layoutEditor.setXOverShort(Math.round((0.5 * lng) + 0.1));
} else if (getTurnoutType() == TurnoutType.RH_XOVER) {
double distDC = Math.hypot(bX + cX, bY + cY);
layoutEditor.setXOverShort(Math.round((0.25 * distDC) + 0.1));
layoutEditor.setXOverLong(Math.round((0.75 * distDC) + 0.1));
double hwid = Math.sqrt((lenC * lenC) - (0.5625 * distDC * distDC));
layoutEditor.setXOverHWid(Math.round(hwid + 0.1));
} else if (getTurnoutType() == TurnoutType.LH_XOVER) {
double distDC = Math.hypot(bX + cX, bY + cY);
layoutEditor.setXOverShort(Math.round((0.25 * distDC) + 0.1));
layoutEditor.setXOverLong(Math.round((0.75 * distDC) + 0.1));
double hwid = Math.sqrt((lenC * lenC) - (0.0625 * distDC * distDC));
layoutEditor.setXOverHWid(Math.round(hwid + 0.1));
}
}
}
/**
* Set up Layout Block(s) for this Turnout.
*
* @param newLayoutBlock See {@link LayoutTurnout#setLayoutBlock} for
* definition
*/
public void setLayoutBlock(LayoutBlock newLayoutBlock) {
turnout.setLayoutBlock(newLayoutBlock);
// correct any graphical artifacts
setTrackSegmentBlocks();
}
public void setLayoutBlockB(LayoutBlock newLayoutBlock) {
turnout.setLayoutBlockB(newLayoutBlock);
// correct any graphical artifacts
setTrackSegmentBlocks();
}
public void setLayoutBlockC(LayoutBlock newLayoutBlock) {
turnout.setLayoutBlockC(newLayoutBlock);
// correct any graphical artifacts
setTrackSegmentBlocks();
}
public void setLayoutBlockD(LayoutBlock newLayoutBlock) {
turnout.setLayoutBlockD(newLayoutBlock);
// correct any graphical artifacts
setTrackSegmentBlocks();
}
public void setLayoutBlockByName(@Nonnull String name) {
turnout.setLayoutBlockByName(name);
}
public void setLayoutBlockBByName(@Nonnull String name) {
turnout.setLayoutBlockByName(name);
}
public void setLayoutBlockCByName(@Nonnull String name) {
turnout.setLayoutBlockByName(name);
}
public void setLayoutBlockDByName(@Nonnull String name) {
turnout.setLayoutBlockByName(name);
}
/**
* Check each connection point and update the block value for very short
* track segments.
*
* @since 4.11.6
*/
void setTrackSegmentBlocks() {
setTrackSegmentBlock(HitPointType.TURNOUT_A, false);
setTrackSegmentBlock(HitPointType.TURNOUT_B, false);
setTrackSegmentBlock(HitPointType.TURNOUT_C, false);
if (hasEnteringDoubleTrack()) {
setTrackSegmentBlock(HitPointType.TURNOUT_D, false);
}
}
/**
* Update the block for a track segment that provides a (graphically) short
* connection between a turnout and another object, normally another
* turnout. These are hard to see and are frequently missed.
* <p>
* Skip block changes if signal heads, masts or sensors have been assigned.
* Only track segments with a length less than the turnout circle radius
* will be changed.
*
* @since 4.11.6
* @param pointType The point type which indicates which turnout
* connection.
* @param isAutomatic True for the automatically generated track segment
* created by the drag-n-drop process. False for existing
* connections which require a track segment length
* calculation.
*/
void setTrackSegmentBlock(HitPointType pointType, boolean isAutomatic) {
TrackSegment trkSeg;
Point2D pointCoord;
LayoutBlock blockA = getLayoutBlock();
LayoutBlock blockB = getLayoutBlock();
LayoutBlock blockC = getLayoutBlock();
LayoutBlock blockD = getLayoutBlock();
LayoutBlock currBlk = blockA;
switch (pointType) {
case TURNOUT_A:
case SLIP_A:
if (signalA1HeadNamed != null) {
return;
}
if (signalA2HeadNamed != null) {
return;
}
if (signalA3HeadNamed != null) {
return;
}
if (getSignalAMast() != null) {
return;
}
if (getSensorA() != null) {
return;
}
trkSeg = (TrackSegment) getConnectA();
pointCoord = getCoordsA();
break;
case TURNOUT_B:
case SLIP_B:
if (signalB1HeadNamed != null) {
return;
}
if (signalB2HeadNamed != null) {
return;
}
if (getSignalBMast() != null) {
return;
}
if (getSensorB() != null) {
return;
}
trkSeg = (TrackSegment) getConnectB();
pointCoord = getCoordsB();
if (isTurnoutTypeXover()) {
currBlk = blockB != null ? blockB : blockA;
}
break;
case TURNOUT_C:
case SLIP_C:
if (signalC1HeadNamed != null) {
return;
}
if (signalC2HeadNamed != null) {
return;
}
if (getSignalCMast() != null) {
return;
}
if (getSensorC() != null) {
return;
}
trkSeg = (TrackSegment) getConnectC();
pointCoord = getCoordsC();
if (isTurnoutTypeXover()) {
currBlk = blockC != null ? blockC : blockA;
}
break;
case TURNOUT_D:
case SLIP_D:
if (signalD1HeadNamed != null) {
return;
}
if (signalD2HeadNamed != null) {
return;
}
if (getSignalDMast() != null) {
return;
}
if (getSensorD() != null) {
return;
}
trkSeg = (TrackSegment) getConnectD();
pointCoord = getCoordsD();
if (isTurnoutTypeXover()) {
currBlk = blockD != null ? blockD : blockA;
}
break;
default:
log.error("{}.setTrackSegmentBlock({}, {}); Invalid pointType",
getName(), pointType, isAutomatic ? "AUTO" : "NON-AUTO");
return;
}
if (trkSeg != null) {
double chkSize = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
double segLength = 0;
if (!isAutomatic) {
Point2D segCenter = getCoordsCenter();
segLength = MathUtil.distance(pointCoord, segCenter) * 2;
}
if (segLength < chkSize) {
log.debug("Set block:");
log.debug(" seg: {}", trkSeg);
log.debug(" cor: {}", pointCoord);
log.debug(" blk: {}", (currBlk == null) ? "null" : currBlk.getDisplayName());
log.debug(" len: {}", segLength);
trkSeg.setLayoutBlock(currBlk);
layoutEditor.getLEAuxTools().setBlockConnectivityChanged();
}
}
}
/**
* Test if turnout legs are mainline track or not.
*
* @return true if connecting track segment is mainline; Defaults to not
* mainline if connecting track segment is missing
*/
public boolean isMainlineA() {
return turnout.isMainlineA();
}
public boolean isMainlineB() {
return turnout.isMainlineB();
}
public boolean isMainlineC() {
return turnout.isMainlineC();
}
public boolean isMainlineD() {
return turnout.isMainlineD();
}
/**
* {@inheritDoc}
*/
@Override
protected HitPointType findHitPointType(@Nonnull Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) {
HitPointType result = HitPointType.NONE; // assume point not on connection
// note: optimization here: instead of creating rectangles for all the
// points to check below, we create a rectangle for the test point
// and test if the points below are in that rectangle instead.
Rectangle2D r = layoutEditor.layoutEditorControlCircleRectAt(hitPoint);
Point2D p, minPoint = MathUtil.zeroPoint2D;
double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
double distance, minDistance = POSITIVE_INFINITY;
// check center coordinates
if (!requireUnconnected) {
p = getCoordsCenter();
distance = MathUtil.distance(p, hitPoint);
if (distance < minDistance) {
minDistance = distance;
minPoint = p;
result = HitPointType.TURNOUT_CENTER;
}
}
// check the A connection point
if (!requireUnconnected || (getConnectA() == null)) {
p = getCoordsA();
distance = MathUtil.distance(p, hitPoint);
if (distance < minDistance) {
minDistance = distance;
minPoint = p;
result = HitPointType.TURNOUT_A;
}
}
// check the B connection point
if (!requireUnconnected || (getConnectB() == null)) {
p = getCoordsB();
distance = MathUtil.distance(p, hitPoint);
if (distance < minDistance) {
minDistance = distance;
minPoint = p;
result = HitPointType.TURNOUT_B;
}
}
// check the C connection point
if (!requireUnconnected || (getConnectC() == null)) {
p = getCoordsC();
distance = MathUtil.distance(p, hitPoint);
if (distance < minDistance) {
minDistance = distance;
minPoint = p;
result = HitPointType.TURNOUT_C;
}
}
// check the D connection point
if (isTurnoutTypeXover()) {
if (!requireUnconnected || (getConnectD() == null)) {
p = getCoordsD();
distance = MathUtil.distance(p, hitPoint);
if (distance < minDistance) {
minDistance = distance;
minPoint = p;
result = HitPointType.TURNOUT_D;
}
}
}
if ((useRectangles && !r.contains(minPoint))
|| (!useRectangles && (minDistance > circleRadius))) {
result = HitPointType.NONE;
}
return result;
} // findHitPointType
/*
* Modify coordinates methods
*/
/**
* {@inheritDoc}
*/
@Override
public void setCoordsCenter(@Nonnull Point2D p) {
Point2D offset = MathUtil.subtract(p, getCoordsCenter());
pointA = MathUtil.add(pointA, offset);
pointB = MathUtil.add(pointB, offset);
pointC = MathUtil.add(pointC, offset);
pointD = MathUtil.add(pointD, offset);
super.setCoordsCenter(p);
}
// temporary should be private once LayoutTurnout no longer needs it
void reCalculateCenter() {
super.setCoordsCenter(MathUtil.midPoint(pointA, pointC));
}
public void setCoordsA(@Nonnull Point2D p) {
pointA = p;
if (version == 2) {
reCalculateCenter();
}
double x = getCoordsCenter().getX() - p.getX();
double y = getCoordsCenter().getY() - p.getY();
if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) {
dispA = new Point2D.Double(x, y);
// adjust to maintain rectangle
double oldLength = MathUtil.length(dispB);
double newLength = Math.hypot(x, y);
dispB = MathUtil.multiply(dispB, newLength / oldLength);
} else if ((getTurnoutType() == TurnoutType.RH_XOVER)
|| (getTurnoutType() == TurnoutType.LH_XOVER)) {
dispA = new Point2D.Double(x, y);
// adjust to maintain the parallelogram
double a = 0.0;
double b = -y;
double xi = 0.0;
double yi = b;
if ((dispB.getX() + x) != 0.0) {
a = (dispB.getY() + y) / (dispB.getX() + x);
b = -y + (a * x);
xi = -b / (a + (1.0 / a));
yi = (a * xi) + b;
}
if (getTurnoutType() == TurnoutType.RH_XOVER) {
x = xi - (0.333333 * (-x - xi));
y = yi - (0.333333 * (-y - yi));
} else if (getTurnoutType() == TurnoutType.LH_XOVER) {
x = xi - (3.0 * (-x - xi));
y = yi - (3.0 * (-y - yi));
}
dispB = new Point2D.Double(x, y);
} else if (getTurnoutType() == TurnoutType.WYE_TURNOUT) {
// modify both to maintain same angle at wye
double temX = (dispB.getX() + dispA.getX());
double temY = (dispB.getY() + dispA.getY());
double temXx = (dispB.getX() - dispA.getX());
double temYy = (dispB.getY() - dispA.getY());
double tan = Math.sqrt(((temX * temX) + (temY * temY))
/ ((temXx * temXx) + (temYy * temYy)));
double xx = x + (y / tan);
double yy = y - (x / tan);
dispA = new Point2D.Double(xx, yy);
xx = x - (y / tan);
yy = y + (x / tan);
dispB = new Point2D.Double(xx, yy);
} else {
dispB = new Point2D.Double(x, y);
}
}
public void setCoordsB(Point2D p) {
pointB = p;
double x = getCoordsCenter().getX() - p.getX();
double y = getCoordsCenter().getY() - p.getY();
dispB = new Point2D.Double(-x, -y);
if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER)
|| (getTurnoutType() == TurnoutType.WYE_TURNOUT)) {
// adjust to maintain rectangle or wye shape
double oldLength = MathUtil.length(dispA);
double newLength = Math.hypot(x, y);
dispA = MathUtil.multiply(dispA, newLength / oldLength);
} else if ((getTurnoutType() == TurnoutType.RH_XOVER)
|| (getTurnoutType() == TurnoutType.LH_XOVER)) {
// adjust to maintain the parallelogram
double a = 0.0;
double b = y;
double xi = 0.0;
double yi = b;
if ((dispA.getX() - x) != 0.0) {
if ((-dispA.getX() + x) == 0) {
/* we can in some situations eg 90' vertical end up with a 0 value,
so hence remove a small amount so that we
don't have a divide by zero issue */
x = x - 0.0000000001;
}
a = (dispA.getY() - y) / (dispA.getX() - x);
b = y - (a * x);
xi = -b / (a + (1.0 / a));
yi = (a * xi) + b;
}
if (getTurnoutType() == TurnoutType.LH_XOVER) {
x = xi - (0.333333 * (x - xi));
y = yi - (0.333333 * (y - yi));
} else if (getTurnoutType() == TurnoutType.RH_XOVER) {
x = xi - (3.0 * (x - xi));
y = yi - (3.0 * (y - yi));
}
dispA = new Point2D.Double(x, y);
}
}
public void setCoordsC(Point2D p) {
pointC = p;
if (version == 2) {
reCalculateCenter();
}
double x = getCoordsCenter().getX() - p.getX();
double y = getCoordsCenter().getY() - p.getY();
dispA = new Point2D.Double(-x, -y);
if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER)
|| (getTurnoutType() == TurnoutType.WYE_TURNOUT)) {
// adjust to maintain rectangle or wye shape
double oldLength = MathUtil.length(dispB);
double newLength = Math.hypot(x, y);
dispB = MathUtil.multiply(dispB, newLength / oldLength);
} else if ((getTurnoutType() == TurnoutType.RH_XOVER)
|| (getTurnoutType() == TurnoutType.LH_XOVER)) {
double a = 0.0;
double b = -y;
double xi = 0.0;
double yi = b;
if ((dispB.getX() + x) != 0.0) {
if ((-dispB.getX() + x) == 0) {
/* we can in some situations eg 90' vertical end up with a 0 value,
so hence remove a small amount so that we
don't have a divide by zero issue */
x = x - 0.0000000001;
}
a = (-dispB.getY() + y) / (-dispB.getX() + x);
b = -y + (a * x);
xi = -b / (a + (1.0 / a));
yi = (a * xi) + b;
}
if (getTurnoutType() == TurnoutType.RH_XOVER) {
x = xi - (0.333333 * (-x - xi));
y = yi - (0.333333 * (-y - yi));
} else if (getTurnoutType() == TurnoutType.LH_XOVER) {
x = xi - (3.0 * (-x - xi));
y = yi - (3.0 * (-y - yi));
}
dispB = new Point2D.Double(-x, -y);
}
}
public void setCoordsD(Point2D p) {
pointD = p;
// only used for crossovers
double x = getCoordsCenter().getX() - p.getX();
double y = getCoordsCenter().getY() - p.getY();
dispB = new Point2D.Double(x, y);
if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) {
// adjust to maintain rectangle
double oldLength = MathUtil.length(dispA);
double newLength = Math.hypot(x, y);
dispA = MathUtil.multiply(dispA, newLength / oldLength);
} else if ((getTurnoutType() == TurnoutType.RH_XOVER)
|| (getTurnoutType() == TurnoutType.LH_XOVER)) {
// adjust to maintain the parallelogram
double a = 0.0;
double b = y;
double xi = 0.0;
double yi = b;
if ((dispA.getX() + x) != 0.0) {
a = (dispA.getY() + y) / (dispA.getX() + x);
b = -y + (a * x);
xi = -b / (a + (1.0 / a));
yi = (a * xi) + b;
}
if (getTurnoutType() == TurnoutType.LH_XOVER) {
x = xi - (0.333333 * (-x - xi));
y = yi - (0.333333 * (-y - yi));
} else if (getTurnoutType() == TurnoutType.RH_XOVER) {
x = xi - (3.0 * (-x - xi));
y = yi - (3.0 * (-y - yi));
}
dispA = new Point2D.Double(x, y);
}
}
/**
* {@inheritDoc}
*/
@Override
public void scaleCoords(double xFactor, double yFactor) {
Point2D factor = new Point2D.Double(xFactor, yFactor);
super.setCoordsCenter(MathUtil.granulize(MathUtil.multiply(getCoordsCenter(), factor), 1.0));
dispA = MathUtil.granulize(MathUtil.multiply(dispA, factor), 1.0);
dispB = MathUtil.granulize(MathUtil.multiply(dispB, factor), 1.0);
pointA = MathUtil.granulize(MathUtil.multiply(pointA, factor), 1.0);
pointB = MathUtil.granulize(MathUtil.multiply(pointB, factor), 1.0);
pointC = MathUtil.granulize(MathUtil.multiply(pointC, factor), 1.0);
pointD = MathUtil.granulize(MathUtil.multiply(pointD, factor), 1.0);
}
/**
* {@inheritDoc}
*/
@Override
public void translateCoords(double xFactor, double yFactor) {
Point2D factor = new Point2D.Double(xFactor, yFactor);
super.setCoordsCenter(MathUtil.add(getCoordsCenter(), factor));
pointA = MathUtil.add(pointA, factor);
pointB = MathUtil.add(pointB, factor);
pointC = MathUtil.add(pointC, factor);
pointD = MathUtil.add(pointD, factor);
}
/**
* {@inheritDoc}
*/
@Override
public void rotateCoords(double angleDEG) {
// rotate coordinates
double rotRAD = Math.toRadians(angleDEG);
double sineRot = Math.sin(rotRAD);
double cosineRot = Math.cos(rotRAD);
// rotate displacements around origin {0, 0}
Point2D center_temp = getCoordsCenter();
super.setCoordsCenter(MathUtil.zeroPoint2D);
dispA = rotatePoint(dispA, sineRot, cosineRot);
dispB = rotatePoint(dispB, sineRot, cosineRot);
super.setCoordsCenter(center_temp);
pointA = rotatePoint(pointA, sineRot, cosineRot);
pointB = rotatePoint(pointB, sineRot, cosineRot);
pointC = rotatePoint(pointC, sineRot, cosineRot);
pointD = rotatePoint(pointD, sineRot, cosineRot);
}
public double getRotationDEG() {
double result = 0;
switch (getTurnoutType()) {
case RH_TURNOUT:
case LH_TURNOUT:
case WYE_TURNOUT: {
result = 90 - MathUtil.computeAngleDEG(getCoordsA(), getCoordsCenter());
break;
}
case DOUBLE_XOVER:
case RH_XOVER:
case LH_XOVER: {
result = 90 - MathUtil.computeAngleDEG(getCoordsA(), getCoordsB());
break;
}
default: {
break;
}
}
return result;
}
/**
* Toggle turnout if clicked on, physical turnout exists, and not disabled.
*/
public void toggleTurnout() {
turnout.toggleTurnout();
}
/**
* Set the LayoutTurnout state. Used for sending the toggle command Checks
* not disabled, disable when occupied Also sets secondary Turnout commanded
* state
*
* @param state New state to set, eg Turnout.CLOSED
*/
public void setState(int state) {
turnout.setState(state);
}
/**
* Get the LayoutTurnout state
* <p>
* Ensures the secondary Turnout state matches the primary
*
* @return the state, eg Turnout.CLOSED or Turnout.INCONSISTENT
*/
public int getState() {
return turnout.getState();
}
/**
* Is this turnout occupied?
*
* @return true if occupied
*/
private boolean isOccupied() {
return turnout.isOccupied();
}
// initialization instance variables (used when loading a LayoutEditor)
public String connectAName = "";
public String connectBName = "";
public String connectCName = "";
public String connectDName = "";
public String tBlockAName = "";
public String tBlockBName = "";
public String tBlockCName = "";
public String tBlockDName = "";
private JPopupMenu popup = null;
/**
* {@inheritDoc}
*/
@Override
@Nonnull
protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent) {
if (popup != null) {
popup.removeAll();
} else {
popup = new JPopupMenu();
}
if (layoutEditor.isEditable()) {
String label = "";
switch (getTurnoutType()) {
case RH_TURNOUT:
label = Bundle.getMessage("RightTurnout");
break;
case LH_TURNOUT:
label = Bundle.getMessage("LeftTurnout");
break;
case WYE_TURNOUT:
label = Bundle.getMessage("WYETurnout");
break;
case DOUBLE_XOVER:
label = Bundle.getMessage("DoubleCrossover");
break;
case RH_XOVER:
label = Bundle.getMessage("RightCrossover");
break;
case LH_XOVER:
label = Bundle.getMessage("LeftCrossover");
break;
default:
break;
}
JMenuItem jmi = popup.add(Bundle.getMessage("MakeLabel", label) + getName());
jmi.setEnabled(false);
if (getTurnout() == null) {
jmi = popup.add(Bundle.getMessage("NoTurnout"));
} else {
String stateString = getTurnoutStateString(getTurnout().getKnownState());
stateString = String.format(" (%s)", stateString);
jmi = popup.add(Bundle.getMessage("BeanNameTurnout")
+ ": " + getTurnoutName() + stateString);
}
jmi.setEnabled(false);
if (getSecondTurnout() != null) {
String stateString = getTurnoutStateString(getSecondTurnout().getKnownState());
stateString = String.format(" (%s)", stateString);
jmi = popup.add(Bundle.getMessage("Supporting",
Bundle.getMessage("BeanNameTurnout"))
+ ": " + getSecondTurnoutName() + stateString);
}
jmi.setEnabled(false);
if (getBlockName().isEmpty()) {
jmi = popup.add(Bundle.getMessage("NoBlock"));
jmi.setEnabled(false);
} else {
jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + getLayoutBlock().getDisplayName());
jmi.setEnabled(false);
if (isTurnoutTypeXover()) {
// check if extra blocks have been entered
if ((getLayoutBlockB() != null) && (getLayoutBlockB() != getLayoutBlock())) {
jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "B")) + getLayoutBlockB().getDisplayName());
jmi.setEnabled(false);
}
if ((getLayoutBlockC() != null) && (getLayoutBlockC() != getLayoutBlock())) {
jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "C")) + getLayoutBlockC().getDisplayName());
jmi.setEnabled(false);
}
if ((getLayoutBlockD() != null) && (getLayoutBlockD() != getLayoutBlock())) {
jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "D")) + getLayoutBlockD().getDisplayName());
jmi.setEnabled(false);
}
}
}
// if there are any track connections
if ((getConnectA() != null) || (getConnectB() != null)
|| (getConnectC() != null) || (getConnectD() != null)) {
JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections")); // there is no pane opening (which is what ... implies)
if (getConnectA() != null) {
connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "A") + getConnectA().getName()) {
@Override
public void actionPerformed(ActionEvent e) {
LayoutEditorFindItems lf = layoutEditor.getFinder();
LayoutTrack lt = lf.findObjectByName(getConnectA().getName());
// this shouldn't ever be null... however...
if (lt != null) {
LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
layoutEditor.setSelectionRect(ltv.getBounds());
ltv.showPopup();
}
}
});
}
if (getConnectB() != null) {
connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "B") + getConnectB().getName()) {
@Override
public void actionPerformed(ActionEvent e) {
LayoutEditorFindItems lf = layoutEditor.getFinder();
LayoutTrack lt = lf.findObjectByName(getConnectB().getName());
// this shouldn't ever be null... however...
if (lt != null) {
LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
layoutEditor.setSelectionRect(ltv.getBounds());
ltv.showPopup();
}
}
});
}
if (getConnectC() != null) {
connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "C") + getConnectC().getName()) {
@Override
public void actionPerformed(ActionEvent e) {
LayoutEditorFindItems lf = layoutEditor.getFinder();
LayoutTrack lt = lf.findObjectByName(getConnectC().getName());
// this shouldn't ever be null... however...
if (lt != null) {
LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
layoutEditor.setSelectionRect(ltv.getBounds());
ltv.showPopup();
}
}
});
}
if (getConnectD() != null) {
connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "D") + getConnectD().getName()) {
@Override
public void actionPerformed(ActionEvent e) {
LayoutEditorFindItems lf = layoutEditor.getFinder();
LayoutTrack lt = lf.findObjectByName(getConnectD().getName());
// this shouldn't ever be null... however...
if (lt != null) {
LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
layoutEditor.setSelectionRect(ltv.getBounds());
ltv.showPopup();
}
}
});
}
popup.add(connectionsMenu);
}
popup.add(new JSeparator(JSeparator.HORIZONTAL));
JCheckBoxMenuItem hiddenCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("Hidden"));
hiddenCheckBoxMenuItem.setSelected(isHidden());
popup.add(hiddenCheckBoxMenuItem);
hiddenCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e1) -> {
JCheckBoxMenuItem o = (JCheckBoxMenuItem) e1.getSource();
setHidden(o.isSelected());
});
JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(Bundle.getMessage("Disabled"));
cbmi.setSelected(isDisabled());
popup.add(cbmi);
cbmi.addActionListener((java.awt.event.ActionEvent e2) -> {
JCheckBoxMenuItem o = (JCheckBoxMenuItem) e2.getSource();
setDisabled(o.isSelected());
});
cbmi = new JCheckBoxMenuItem(Bundle.getMessage("DisabledWhenOccupied"));
if (getTurnout() == null || getBlockName().isEmpty()) {
cbmi.setEnabled(false);
}
cbmi.setSelected(isDisabledWhenOccupied());
popup.add(cbmi);
cbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
JCheckBoxMenuItem o = (JCheckBoxMenuItem) e3.getSource();
setDisableWhenOccupied(o.isSelected());
});
// Rotate if there are no track connections
// if ((getConnectA() == null) && (getConnectB() == null)
// && (getConnectC() == null)
// && (getConnectD() == null))
{
JMenuItem rotateItem = new JMenuItem(Bundle.getMessage("Rotate_",
String.format("%.1f", getRotationDEG())) + "...");
popup.add(rotateItem);
rotateItem.addActionListener((ActionEvent event) -> {
boolean entering = true;
boolean error = false;
String newAngle = "";
while (entering) {
// prompt for rotation angle
error = false;
newAngle = JmriJOptionPane.showInputDialog(layoutEditor,
Bundle.getMessage("MakeLabel", Bundle.getMessage("EnterRotation")),"");
if (newAngle==null || newAngle.isEmpty()) {
return; // cancelled
}
double rot = 0.0;
try {
rot = Double.parseDouble(newAngle);
} catch (Exception e1) {
JmriJOptionPane.showMessageDialog(layoutEditor, Bundle.getMessage("Error3")
+ " " + e1, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
error = true;
newAngle = "";
}
if (!error) {
entering = false;
if (rot != 0.0) {
rotateCoords(rot);
layoutEditor.redrawPanel();
}
}
}
});
}
popup.add(new AbstractAction(Bundle.getMessage("UseSizeAsDefault")) {
@Override
public void actionPerformed(ActionEvent e) {
setUpDefaultSize();
}
});
popup.add(new AbstractAction(Bundle.getMessage("ButtonEdit")) {
@Override
public void actionPerformed(ActionEvent e) {
editor.editLayoutTrack(LayoutTurnoutView.this);
}
});
popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) {
@Override
public void actionPerformed(ActionEvent e) {
if (canRemove() && removeInlineLogixNG()
&& layoutEditor.removeLayoutTurnout(turnout)) {
// Returned true if user did not cancel
remove();
dispose();
}
}
});
if (getTurnout() != null) {
AbstractAction ssaa = new AbstractAction(Bundle.getMessage("SetSignals")) {
@Override
public void actionPerformed(ActionEvent e) {
LayoutEditorTools tools = layoutEditor.getLETools();
LayoutEditorToolBarPanel letbp = getLayoutEditorToolBarPanel();
if (isTurnoutTypeXover()) {
tools.setSignalsAtXoverTurnoutFromMenu(turnout,
letbp.signalIconEditor, letbp.signalFrame);
} else if (getLinkType() == LinkType.NO_LINK) {
tools.setSignalsAtTurnoutFromMenu(turnout,
letbp.signalIconEditor, letbp.signalFrame);
} else if (getLinkType() == LinkType.THROAT_TO_THROAT) {
tools.setSignalsAtThroatToThroatTurnoutsFromMenu(turnout, getLinkedTurnoutName(),
letbp.signalIconEditor, letbp.signalFrame);
} else if (getLinkType() == LinkType.FIRST_3_WAY) {
tools.setSignalsAt3WayTurnoutFromMenu(getTurnoutName(), getLinkedTurnoutName(),
letbp.signalIconEditor, letbp.signalFrame);
} else if (getLinkType() == LinkType.SECOND_3_WAY) {
tools.setSignalsAt3WayTurnoutFromMenu(getLinkedTurnoutName(), getTurnoutName(),
letbp.signalIconEditor, letbp.signalFrame);
}
}
};
JMenu jm = new JMenu(Bundle.getMessage("SignalHeads"));
if (layoutEditor.getLETools().addLayoutTurnoutSignalHeadInfoToMenu(
getTurnoutName(), getLinkedTurnoutName(), jm)) {
jm.add(ssaa);
popup.add(jm);
} else {
popup.add(ssaa);
}
}
if (!getBlockName().isEmpty()) {
final String[] boundaryBetween = getBlockBoundaries();
boolean blockBoundaries = false;
for (int i = 0; i < 4; i++) {
if (boundaryBetween[i] != null) {
blockBoundaries = true;
}
}
if (blockBoundaries) {
popup.add(new AbstractAction(Bundle.getMessage("SetSignalMasts")) {
@Override
public void actionPerformed(ActionEvent e) {
layoutEditor.getLETools().setSignalMastsAtTurnoutFromMenu(turnout,
boundaryBetween);
}
});
popup.add(new AbstractAction(Bundle.getMessage("SetSensors")) {
@Override
public void actionPerformed(ActionEvent e) {
LayoutEditorToolBarPanel letbp = getLayoutEditorToolBarPanel();
layoutEditor.getLETools().setSensorsAtTurnoutFromMenu(
turnout,
boundaryBetween,
letbp.sensorIconEditor,
letbp.sensorFrame);
}
});
}
if (InstanceManager.getDefault(LayoutBlockManager.class
).isAdvancedRoutingEnabled()) {
Map<String, LayoutBlock> map = new HashMap<>();
if (!getBlockName().isEmpty()) {
map.put(getBlockName(), getLayoutBlock());
}
if (!getBlockBName().isEmpty()) {
map.put(getBlockBName(), getLayoutBlockB());
}
if (!getBlockCName().isEmpty()) {
map.put(getBlockCName(), getLayoutBlockC());
}
if (!getBlockDName().isEmpty()) {
map.put(getBlockDName(), getLayoutBlockD());
}
if (blockBoundaries) {
if (map.size() == 1) {
popup.add(new AbstractAction(Bundle.getMessage("ViewBlockRouting")) {
@Override
public void actionPerformed(ActionEvent e) {
AbstractAction routeTableAction = new LayoutBlockRouteTableAction("ViewRouting", getLayoutBlock());
routeTableAction.actionPerformed(e);
}
});
} else if (map.size() > 1) {
JMenu viewRouting = new JMenu(Bundle.getMessage("ViewBlockRouting"));
for (Map.Entry<String, LayoutBlock> entry : map.entrySet()) {
String blockName = entry.getKey();
LayoutBlock layoutBlock = entry.getValue();
viewRouting.add(new AbstractActionImpl(blockName, getBlockBName(), layoutBlock));
}
popup.add(viewRouting);
}
} // if (blockBoundaries)
} // .isAdvancedRoutingEnabled()
} // getBlockName().isEmpty()
setAdditionalEditPopUpMenu(popup);
layoutEditor.setShowAlignmentMenu(popup);
addCommonPopupItems(mouseEvent, popup);
popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
} else if (!viewAdditionalMenu.isEmpty()) {
setAdditionalViewPopUpMenu(popup);
addCommonPopupItems(mouseEvent, popup);
popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
}
return popup;
} // showPopup
public String[] getBlockBoundaries() {
return turnout.getBlockBoundaries();
}
public ArrayList<LayoutBlock> getProtectedBlocks(jmri.NamedBean bean) {
return turnout.getProtectedBlocks(bean);
}
protected void removeSML(SignalMast signalMast) {
turnout.removeSML(signalMast);
}
/**
* Clean up when this object is no longer needed. Should not be called while
* the object is still displayed; see {@link #remove()}
*/
public void dispose() {
if (popup != null) {
popup.removeAll();
}
popup = null;
}
/**
* Remove this object from display and persistance.
*/
public void remove() {
turnout.remove();
}
/**
* "active" means that the object is still displayed, and should be stored.
*
* @return true if active
*/
public boolean isActive() {
return turnout.isActive();
}
ArrayList<JMenuItem> editAdditionalMenu = new ArrayList<>(0);
ArrayList<JMenuItem> viewAdditionalMenu = new ArrayList<>(0);
public void addEditPopUpMenu(JMenuItem menu) {
if (!editAdditionalMenu.contains(menu)) {
editAdditionalMenu.add(menu);
}
}
public void addViewPopUpMenu(JMenuItem menu) {
if (!viewAdditionalMenu.contains(menu)) {
viewAdditionalMenu.add(menu);
}
}
public void setAdditionalEditPopUpMenu(JPopupMenu popup) {
if (editAdditionalMenu.isEmpty()) {
return;
}
popup.addSeparator();
for (JMenuItem mi : editAdditionalMenu) {
popup.add(mi);
}
}
public void setAdditionalViewPopUpMenu(JPopupMenu popup) {
if (viewAdditionalMenu.isEmpty()) {
return;
}
popup.addSeparator();
for (JMenuItem mi : viewAdditionalMenu) {
popup.add(mi);
}
}
/**
* Draw track decorations.
* <p>
* This type of track has none, so this method is empty.
*/
@Override
protected void drawDecorations(Graphics2D g2) {
}
/**
* {@inheritDoc}
*/
@Override
protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) {
if (isBlock && getLayoutBlock() == null) {
// Skip the block layer if there is no block assigned.
return;
}
Point2D pA = getCoordsA();
Point2D pB = getCoordsB();
Point2D pC = getCoordsC();
Point2D pD = getCoordsD();
boolean mainlineA = isMainlineA();
boolean mainlineB = isMainlineB();
boolean mainlineC = isMainlineC();
boolean mainlineD = isMainlineD();
boolean drawUnselectedLeg = layoutEditor.isTurnoutDrawUnselectedLeg();
Color color = g2.getColor();
// if this isn't a block line all these will be the same color
Color colorA = color;
Color colorB = color;
Color colorC = color;
Color colorD = color;
if (isBlock) {
LayoutBlock lb = getLayoutBlock();
colorA = (lb == null) ? color : lb.getBlockColor();
lb = getLayoutBlockB();
colorB = (lb == null) ? color : lb.getBlockColor();
lb = getLayoutBlockC();
colorC = (lb == null) ? color : lb.getBlockColor();
lb = getLayoutBlockD();
colorD = (lb == null) ? color : lb.getBlockColor();
}
// middles
Point2D pM = getCoordsCenter();
Point2D pABM = MathUtil.midPoint(pA, pB);
Point2D pAM = MathUtil.lerp(pA, pABM, 5.0 / 8.0);
Point2D pAMP = MathUtil.midPoint(pAM, pABM);
Point2D pBM = MathUtil.lerp(pB, pABM, 5.0 / 8.0);
Point2D pBMP = MathUtil.midPoint(pBM, pABM);
Point2D pCDM = MathUtil.midPoint(pC, pD);
Point2D pCM = MathUtil.lerp(pC, pCDM, 5.0 / 8.0);
Point2D pCMP = MathUtil.midPoint(pCM, pCDM);
Point2D pDM = MathUtil.lerp(pD, pCDM, 5.0 / 8.0);
Point2D pDMP = MathUtil.midPoint(pDM, pCDM);
Point2D pAF = MathUtil.midPoint(pAM, pM);
Point2D pBF = MathUtil.midPoint(pBM, pM);
Point2D pCF = MathUtil.midPoint(pCM, pM);
Point2D pDF = MathUtil.midPoint(pDM, pM);
int state = UNKNOWN;
if (layoutEditor.isAnimating()) {
state = getState();
}
TurnoutType type = getTurnoutType();
if (type == TurnoutType.DOUBLE_XOVER) {
if (state != Turnout.THROWN && state != INCONSISTENT) { // unknown or continuing path - not crossed over
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pABM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pAF, pM));
}
}
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pB, pABM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pBF, pM));
}
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pC, pCDM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pCF, pM));
}
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pD, pCDM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pDF, pM));
}
}
}
if (state != Turnout.CLOSED && state != INCONSISTENT) { // unknown or diverting path - crossed over
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pAM));
g2.draw(new Line2D.Double(pAM, pM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pAMP, pABM));
}
}
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pB, pBM));
g2.draw(new Line2D.Double(pBM, pM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pBMP, pABM));
}
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pC, pCM));
g2.draw(new Line2D.Double(pCM, pM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pCMP, pCDM));
}
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pD, pDM));
g2.draw(new Line2D.Double(pDM, pM));
if (!isBlock || drawUnselectedLeg) {
g2.draw(new Line2D.Double(pDMP, pCDM));
}
}
}
if (state == INCONSISTENT) {
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pAM));
}
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pB, pBM));
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pC, pCM));
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pD, pDM));
}
if (!isBlock || drawUnselectedLeg) {
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pAF, pM));
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pCF, pM));
}
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pBF, pM));
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pDF, pM));
}
}
}
} else if ((type == TurnoutType.RH_XOVER)
|| (type == TurnoutType.LH_XOVER)) { // draw (rh & lh) cross overs
pAF = MathUtil.midPoint(pABM, pM);
pBF = MathUtil.midPoint(pABM, pM);
pCF = MathUtil.midPoint(pCDM, pM);
pDF = MathUtil.midPoint(pCDM, pM);
if (state != Turnout.THROWN && state != INCONSISTENT) { // unknown or continuing path - not crossed over
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pABM));
}
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pABM, pB));
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pC, pCDM));
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pCDM, pD));
}
if (!isBlock || drawUnselectedLeg) {
if (getTurnoutType() == TurnoutType.RH_XOVER) {
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pAF, pM));
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pCF, pM));
}
} else if (getTurnoutType() == TurnoutType.LH_XOVER) {
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pBF, pM));
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pDF, pM));
}
}
}
}
if (state != Turnout.CLOSED && state != INCONSISTENT) { // unknown or diverting path - crossed over
if (getTurnoutType() == TurnoutType.RH_XOVER) {
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pABM));
g2.draw(new Line2D.Double(pABM, pM));
}
if (!isBlock || drawUnselectedLeg) {
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pBM, pB));
}
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pC, pCDM));
g2.draw(new Line2D.Double(pCDM, pM));
}
if (!isBlock || drawUnselectedLeg) {
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pDM, pD));
}
}
} else if (getTurnoutType() == TurnoutType.LH_XOVER) {
if (!isBlock || drawUnselectedLeg) {
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pAM));
}
}
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pB, pABM));
g2.draw(new Line2D.Double(pABM, pM));
}
if (!isBlock || drawUnselectedLeg) {
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pC, pCM));
}
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pD, pCDM));
g2.draw(new Line2D.Double(pCDM, pM));
}
}
}
if (state == INCONSISTENT) {
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pAM));
}
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pB, pBM));
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pC, pCM));
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pD, pDM));
}
if (!isBlock || drawUnselectedLeg) {
if (getTurnoutType() == TurnoutType.RH_XOVER) {
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pAF, pM));
}
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pCF, pM));
}
} else if (getTurnoutType() == TurnoutType.LH_XOVER) {
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pBF, pM));
}
if (isMain == mainlineD) {
g2.setColor(colorD);
g2.draw(new Line2D.Double(pDF, pM));
}
}
}
}
} else if (isTurnoutTypeSlip()) {
log.error("{}.draw1(...); slips should be being drawn by LayoutSlip sub-class", getName());
} else { // LH, RH, or WYE Turnouts
// draw A<===>center
if (isMain == mainlineA) {
g2.setColor(colorA);
g2.draw(new Line2D.Double(pA, pM));
}
if (state == UNKNOWN || (getContinuingSense() == state && state != INCONSISTENT)) { // unknown or continuing path
// draw center<===>B
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(pM, pB));
}
} else if (!isBlock || drawUnselectedLeg) {
// draw center<--=>B
if (isMain == mainlineB) {
g2.setColor(colorB);
g2.draw(new Line2D.Double(MathUtil.twoThirdsPoint(pM, pB), pB));
}
}
if (state == UNKNOWN || (getContinuingSense() != state && state != INCONSISTENT)) { // unknown or diverting path
// draw center<===>C
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(pM, pC));
}
} else if (!isBlock || drawUnselectedLeg) {
// draw center<--=>C
if (isMain == mainlineC) {
g2.setColor(colorC);
g2.draw(new Line2D.Double(MathUtil.twoThirdsPoint(pM, pC), pC));
}
}
}
} // draw1
/**
* {@inheritDoc}
*/
@Override
protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) {
TurnoutType type = getTurnoutType();
Point2D pA = getCoordsA();
Point2D pB = getCoordsB();
Point2D pC = getCoordsC();
Point2D pD = getCoordsD();
Point2D pM = getCoordsCenter();
Point2D vAM = MathUtil.normalize(MathUtil.subtract(pM, pA));
Point2D vAMo = MathUtil.orthogonal(MathUtil.normalize(vAM, railDisplacement));
Point2D pAL = MathUtil.subtract(pA, vAMo);
Point2D pAR = MathUtil.add(pA, vAMo);
Point2D vBM = MathUtil.normalize(MathUtil.subtract(pB, pM));
double dirBM_DEG = MathUtil.computeAngleDEG(vBM);
Point2D vBMo = MathUtil.normalize(MathUtil.orthogonal(vBM), railDisplacement);
Point2D pBL = MathUtil.subtract(pB, vBMo);
Point2D pBR = MathUtil.add(pB, vBMo);
Point2D pMR = MathUtil.add(pM, vBMo);
Point2D vCM = MathUtil.normalize(MathUtil.subtract(pC, pM));
double dirCM_DEG = MathUtil.computeAngleDEG(vCM);
Point2D vCMo = MathUtil.normalize(MathUtil.orthogonal(vCM), railDisplacement);
Point2D pCL = MathUtil.subtract(pC, vCMo);
Point2D pCR = MathUtil.add(pC, vCMo);
Point2D pML = MathUtil.subtract(pM, vBMo);
double deltaBMC_DEG = MathUtil.absDiffAngleDEG(dirBM_DEG, dirCM_DEG);
double deltaBMC_RAD = Math.toRadians(deltaBMC_DEG);
double hypotF = railDisplacement / Math.sin(deltaBMC_RAD / 2.0);
Point2D vDisF = MathUtil.normalize(MathUtil.add(vAM, vCM), hypotF);
if (type == TurnoutType.WYE_TURNOUT) {
vDisF = MathUtil.normalize(vAM, hypotF);
}
Point2D pF = MathUtil.add(pM, vDisF);
Point2D pFR = MathUtil.add(pF, MathUtil.multiply(vBMo, 2.0));
Point2D pFL = MathUtil.subtract(pF, MathUtil.multiply(vCMo, 2.0));
// Point2D pFPR = MathUtil.add(pF, MathUtil.normalize(vBMo, 2.0));
// Point2D pFPL = MathUtil.subtract(pF, MathUtil.normalize(vCMo, 2.0));
Point2D vDisAP = MathUtil.normalize(vAM, hypotF);
Point2D pAP = MathUtil.subtract(pM, vDisAP);
Point2D pAPR = MathUtil.add(pAP, vAMo);
Point2D pAPL = MathUtil.subtract(pAP, vAMo);
// Point2D vSo = MathUtil.normalize(vAMo, 2.0);
// Point2D pSL = MathUtil.add(pAPL, vSo);
// Point2D pSR = MathUtil.subtract(pAPR, vSo);
boolean mainlineA = isMainlineA();
boolean mainlineB = isMainlineB();
boolean mainlineC = isMainlineC();
boolean mainlineD = isMainlineD();
int state = UNKNOWN;
if (layoutEditor.isAnimating()) {
state = getState();
}
switch (type) {
case RH_TURNOUT: {
if (isMain == mainlineA) {
g2.draw(new Line2D.Double(pAL, pML));
g2.draw(new Line2D.Double(pAR, pAPR));
}
if (isMain == mainlineB) {
g2.draw(new Line2D.Double(pML, pBL));
g2.draw(new Line2D.Double(pF, pBR));
if (getContinuingSense() == state) { // unknown or diverting path
// g2.draw(new Line2D.Double(pSR, pFPR));
// } else {
g2.draw(new Line2D.Double(pAPR, pF));
}
}
if (isMain == mainlineC) {
g2.draw(new Line2D.Double(pF, pCL));
g2.draw(new Line2D.Double(pFR, pCR));
GeneralPath path = new GeneralPath();
path.moveTo(pAPR.getX(), pAPR.getY());
path.quadTo(pMR.getX(), pMR.getY(), pFR.getX(), pFR.getY());
path.lineTo(pCR.getX(), pCR.getY());
g2.draw(path);
if (getContinuingSense() != state) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pAPL.getX(), pAPL.getY());
path.quadTo(pML.getX(), pML.getY(), pF.getX(), pF.getY());
g2.draw(path);
// } else {
// path = new GeneralPath();
// path.moveTo(pSL.getX(), pSL.getY());
// path.quadTo(pML.getX(), pML.getY(), pFPL.getX(), pFPL.getY());
// g2.draw(path);
}
}
break;
} // case RH_TURNOUT
case LH_TURNOUT: {
if (isMain == mainlineA) {
g2.draw(new Line2D.Double(pAR, pMR));
g2.draw(new Line2D.Double(pAL, pAPL));
}
if (isMain == mainlineB) {
g2.draw(new Line2D.Double(pMR, pBR));
g2.draw(new Line2D.Double(pF, pBL));
if (getContinuingSense() == state) { // straight path
// g2.draw(new Line2D.Double(pSL, pFPL)); Offset problem
// } else {
g2.draw(new Line2D.Double(pAPL, pF));
}
}
if (isMain == mainlineC) {
g2.draw(new Line2D.Double(pF, pCR));
GeneralPath path = new GeneralPath();
path.moveTo(pAPL.getX(), pAPL.getY());
path.quadTo(pML.getX(), pML.getY(), pFL.getX(), pFL.getY());
path.lineTo(pCL.getX(), pCL.getY());
g2.draw(path);
if (getContinuingSense() != state) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pAPR.getX(), pAPR.getY());
path.quadTo(pMR.getX(), pMR.getY(), pF.getX(), pF.getY());
g2.draw(path);
// } else {
// path = new GeneralPath();
// path.moveTo(pSR.getX(), pSR.getY());
// path.quadTo(pMR.getX(), pMR.getY(), pFPR.getX(), pFPR.getY());
// g2.draw(path);
}
}
break;
} // case LH_TURNOUT
case WYE_TURNOUT: {
if (isMain == mainlineA) {
g2.draw(new Line2D.Double(pAL, pAPL));
g2.draw(new Line2D.Double(pAR, pAPR));
}
if (isMain == mainlineB) {
g2.draw(new Line2D.Double(pF, pBL));
GeneralPath path = new GeneralPath();
path.moveTo(pAPR.getX(), pAPR.getY());
path.quadTo(pMR.getX(), pMR.getY(), pFR.getX(), pFR.getY());
path.lineTo(pBR.getX(), pBR.getY());
g2.draw(path);
if (getContinuingSense() != state) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pAPR.getX(), pAPR.getY());
path.quadTo(pMR.getX(), pMR.getY(), pF.getX(), pF.getY());
g2.draw(path);
// } else {
// path = new GeneralPath();
// path.moveTo(pSR.getX(), pSR.getY());
// path.quadTo(pMR.getX(), pMR.getY(), pFPR.getX(), pFPR.getY());
// bad g2.draw(path);
}
}
if (isMain == mainlineC) {
pML = MathUtil.subtract(pM, vCMo);
GeneralPath path = new GeneralPath();
path.moveTo(pAPL.getX(), pAPL.getY());
path.quadTo(pML.getX(), pML.getY(), pFL.getX(), pFL.getY());
path.lineTo(pCL.getX(), pCL.getY());
g2.draw(path);
g2.draw(new Line2D.Double(pF, pCR));
if (getContinuingSense() != state) { // unknown or diverting path
// path = new GeneralPath();
// path.moveTo(pSL.getX(), pSL.getY());
// path.quadTo(pML.getX(), pML.getY(), pFPL.getX(), pFPL.getY());
// bad g2.draw(path);
} else {
path = new GeneralPath();
path.moveTo(pAPL.getX(), pAPL.getY());
path.quadTo(pML.getX(), pML.getY(), pF.getX(), pF.getY());
g2.draw(path);
}
}
break;
} // case WYE_TURNOUT
case DOUBLE_XOVER: {
// A, B, C, D end points (left and right)
Point2D vAB = MathUtil.normalize(MathUtil.subtract(pB, pA), railDisplacement);
double dirAB_DEG = MathUtil.computeAngleDEG(vAB);
Point2D vABo = MathUtil.orthogonal(MathUtil.normalize(vAB, railDisplacement));
pAL = MathUtil.subtract(pA, vABo);
pAR = MathUtil.add(pA, vABo);
pBL = MathUtil.subtract(pB, vABo);
pBR = MathUtil.add(pB, vABo);
Point2D vCD = MathUtil.normalize(MathUtil.subtract(pD, pC), railDisplacement);
Point2D vCDo = MathUtil.orthogonal(MathUtil.normalize(vCD, railDisplacement));
pCL = MathUtil.add(pC, vCDo);
pCR = MathUtil.subtract(pC, vCDo);
Point2D pDL = MathUtil.add(pD, vCDo);
Point2D pDR = MathUtil.subtract(pD, vCDo);
// AB, CD mid points (left and right)
Point2D pABM = MathUtil.midPoint(pA, pB);
Point2D pABL = MathUtil.midPoint(pAL, pBL);
Point2D pABR = MathUtil.midPoint(pAR, pBR);
Point2D pCDM = MathUtil.midPoint(pC, pD);
Point2D pCDL = MathUtil.midPoint(pCL, pDL);
Point2D pCDR = MathUtil.midPoint(pCR, pDR);
// A, B, C, D mid points
double halfParallelDistance = MathUtil.distance(pABM, pCDM) / 2.0;
Point2D pAM = MathUtil.subtract(pABM, MathUtil.normalize(vAB, halfParallelDistance));
Point2D pAML = MathUtil.subtract(pAM, vABo);
Point2D pAMR = MathUtil.add(pAM, vABo);
Point2D pBM = MathUtil.add(pABM, MathUtil.normalize(vAB, halfParallelDistance));
Point2D pBML = MathUtil.subtract(pBM, vABo);
Point2D pBMR = MathUtil.add(pBM, vABo);
Point2D pCM = MathUtil.subtract(pCDM, MathUtil.normalize(vCD, halfParallelDistance));
Point2D pCML = MathUtil.subtract(pCM, vABo);
Point2D pCMR = MathUtil.add(pCM, vABo);
Point2D pDM = MathUtil.add(pCDM, MathUtil.normalize(vCD, halfParallelDistance));
Point2D pDML = MathUtil.subtract(pDM, vABo);
Point2D pDMR = MathUtil.add(pDM, vABo);
// crossing points
Point2D vACM = MathUtil.normalize(MathUtil.subtract(pCM, pAM), railDisplacement);
Point2D vACMo = MathUtil.orthogonal(vACM);
Point2D vBDM = MathUtil.normalize(MathUtil.subtract(pDM, pBM), railDisplacement);
Point2D vBDMo = MathUtil.orthogonal(vBDM);
Point2D pBDR = MathUtil.add(pM, vACM);
Point2D pBDL = MathUtil.subtract(pM, vACM);
// crossing diamond point (no gaps)
Point2D pVR = MathUtil.add(pBDL, vBDM);
Point2D pKL = MathUtil.subtract(pBDL, vBDM);
Point2D pKR = MathUtil.add(pBDR, vBDM);
Point2D pVL = MathUtil.subtract(pBDR, vBDM);
// crossing diamond points (with gaps)
Point2D vACM2 = MathUtil.normalize(vACM, 2.0);
Point2D vBDM2 = MathUtil.normalize(vBDM, 2.0);
// (syntax of "pKLtC" is "point LK toward C", etc.)
Point2D pKLtC = MathUtil.add(pKL, vACM2);
Point2D pKLtD = MathUtil.add(pKL, vBDM2);
Point2D pVLtA = MathUtil.subtract(pVL, vACM2);
Point2D pVLtD = MathUtil.add(pVL, vBDM2);
Point2D pKRtA = MathUtil.subtract(pKR, vACM2);
Point2D pKRtB = MathUtil.subtract(pKR, vBDM2);
Point2D pVRtB = MathUtil.subtract(pVR, vBDM2);
Point2D pVRtC = MathUtil.add(pVR, vACM2);
// A, B, C, D frog points
vCM = MathUtil.normalize(MathUtil.subtract(pCM, pM));
dirCM_DEG = MathUtil.computeAngleDEG(vCM);
double deltaBAC_DEG = MathUtil.absDiffAngleDEG(dirAB_DEG, dirCM_DEG);
double deltaBAC_RAD = Math.toRadians(deltaBAC_DEG);
hypotF = railDisplacement / Math.sin(deltaBAC_RAD / 2.0);
Point2D vACF = MathUtil.normalize(MathUtil.add(vACM, vAB), hypotF);
Point2D pAFL = MathUtil.add(pAM, vACF);
Point2D pCFR = MathUtil.subtract(pCM, vACF);
Point2D vBDF = MathUtil.normalize(MathUtil.add(vBDM, vCD), hypotF);
Point2D pBFL = MathUtil.add(pBM, vBDF);
Point2D pDFR = MathUtil.subtract(pDM, vBDF);
// A, B, C, D frog points
Point2D pAFR = MathUtil.add(MathUtil.add(pAFL, vACMo), vACMo);
Point2D pBFR = MathUtil.subtract(MathUtil.subtract(pBFL, vBDMo), vBDMo);
Point2D pCFL = MathUtil.subtract(MathUtil.subtract(pCFR, vACMo), vACMo);
Point2D pDFL = MathUtil.add(MathUtil.add(pDFR, vBDMo), vBDMo);
// end of switch rails (closed)
Point2D vABF = MathUtil.normalize(vAB, hypotF);
pAP = MathUtil.subtract(pAM, vABF);
pAPL = MathUtil.subtract(pAP, vABo);
pAPR = MathUtil.add(pAP, vABo);
Point2D pBP = MathUtil.add(pBM, vABF);
Point2D pBPL = MathUtil.subtract(pBP, vABo);
Point2D pBPR = MathUtil.add(pBP, vABo);
Point2D vCDF = MathUtil.normalize(vCD, hypotF);
Point2D pCP = MathUtil.subtract(pCM, vCDF);
Point2D pCPL = MathUtil.add(pCP, vCDo);
Point2D pCPR = MathUtil.subtract(pCP, vCDo);
Point2D pDP = MathUtil.add(pDM, vCDF);
Point2D pDPL = MathUtil.add(pDP, vCDo);
Point2D pDPR = MathUtil.subtract(pDP, vCDo);
// end of switch rails (open)
Point2D vS = MathUtil.normalize(vABo, 2.0);
Point2D pASL = MathUtil.add(pAPL, vS);
// Point2D pASR = MathUtil.subtract(pAPR, vS);
Point2D pBSL = MathUtil.add(pBPL, vS);
// Point2D pBSR = MathUtil.subtract(pBPR, vS);
Point2D pCSR = MathUtil.subtract(pCPR, vS);
// Point2D pCSL = MathUtil.add(pCPL, vS);
Point2D pDSR = MathUtil.subtract(pDPR, vS);
// Point2D pDSL = MathUtil.add(pDPL, vS);
// end of switch rails (open at frogs)
Point2D pAFS = MathUtil.subtract(pAFL, vS);
Point2D pBFS = MathUtil.subtract(pBFL, vS);
Point2D pCFS = MathUtil.add(pCFR, vS);
Point2D pDFS = MathUtil.add(pDFR, vS);
// vSo = MathUtil.orthogonal(vS);
// Point2D pAFSR = MathUtil.add(pAFL, vSo);
// Point2D pBFSR = MathUtil.subtract(pBFL, vSo);
// Point2D pCFSL = MathUtil.subtract(pCFR, vSo);
// Point2D pDFSL = MathUtil.add(pDFR, vSo);
if (isMain == mainlineA) {
g2.draw(new Line2D.Double(pAL, pABL));
g2.draw(new Line2D.Double(pVRtB, pKLtD));
g2.draw(new Line2D.Double(pAFL, pABR));
g2.draw(new Line2D.Double(pAFL, pKL));
GeneralPath path = new GeneralPath();
path.moveTo(pAR.getX(), pAR.getY());
path.lineTo(pAPR.getX(), pAPR.getY());
path.quadTo(pAMR.getX(), pAMR.getY(), pAFR.getX(), pAFR.getY());
path.lineTo(pVR.getX(), pVR.getY());
g2.draw(path);
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pAPL.getX(), pAPL.getY());
path.quadTo(pAML.getX(), pAML.getY(), pAFL.getX(), pAFL.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pASR, pAFSR));
} else { // continuing path
g2.draw(new Line2D.Double(pAPR, pAFL));
path = new GeneralPath();
path.moveTo(pASL.getX(), pASL.getY());
path.quadTo(pAML.getX(), pAML.getY(), pAFS.getX(), pAFS.getY());
// g2.draw(path);
}
}
if (isMain == mainlineB) {
g2.draw(new Line2D.Double(pABL, pBL));
g2.draw(new Line2D.Double(pKLtC, pVLtA));
g2.draw(new Line2D.Double(pBFL, pABR));
g2.draw(new Line2D.Double(pBFL, pKL));
GeneralPath path = new GeneralPath();
path.moveTo(pBR.getX(), pBR.getY());
path.lineTo(pBPR.getX(), pBPR.getY());
path.quadTo(pBMR.getX(), pBMR.getY(), pBFR.getX(), pBFR.getY());
path.lineTo(pVL.getX(), pVL.getY());
g2.draw(path);
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pBPL.getX(), pBPL.getY());
path.quadTo(pBML.getX(), pBML.getY(), pBFL.getX(), pBFL.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pBSR, pBFSR));
} else {
g2.draw(new Line2D.Double(pBPR, pBFL));
path = new GeneralPath();
path.moveTo(pBSL.getX(), pBSL.getY());
path.quadTo(pBML.getX(), pBML.getY(), pBFS.getX(), pBFS.getY());
// g2.draw(path);
}
}
if (isMain == mainlineC) {
g2.draw(new Line2D.Double(pCR, pCDR));
g2.draw(new Line2D.Double(pKRtB, pVLtD));
g2.draw(new Line2D.Double(pCFR, pCDL));
g2.draw(new Line2D.Double(pCFR, pKR));
GeneralPath path = new GeneralPath();
path.moveTo(pCL.getX(), pCL.getY());
path.lineTo(pCPL.getX(), pCPL.getY());
path.quadTo(pCML.getX(), pCML.getY(), pCFL.getX(), pCFL.getY());
path.lineTo(pVL.getX(), pVL.getY());
g2.draw(path);
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pCPR.getX(), pCPR.getY());
path.quadTo(pCMR.getX(), pCMR.getY(), pCFR.getX(), pCFR.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pCSL, pCFSL));
} else {
g2.draw(new Line2D.Double(pCPL, pCFR));
path = new GeneralPath();
path.moveTo(pCSR.getX(), pCSR.getY());
path.quadTo(pCMR.getX(), pCMR.getY(), pCFS.getX(), pCFS.getY());
// g2.draw(path);
}
}
if (isMain == mainlineD) {
g2.draw(new Line2D.Double(pCDR, pDR));
g2.draw(new Line2D.Double(pKRtA, pVRtC));
g2.draw(new Line2D.Double(pDFR, pCDL));
g2.draw(new Line2D.Double(pDFR, pKR));
GeneralPath path = new GeneralPath();
path.moveTo(pDL.getX(), pDL.getY());
path.lineTo(pDPL.getX(), pDPL.getY());
path.quadTo(pDML.getX(), pDML.getY(), pDFL.getX(), pDFL.getY());
path.lineTo(pVR.getX(), pVR.getY());
g2.draw(path);
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pDPR.getX(), pDPR.getY());
path.quadTo(pDMR.getX(), pDMR.getY(), pDFR.getX(), pDFR.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pDSL, pDFSL));
} else {
g2.draw(new Line2D.Double(pDPL, pDFR));
path = new GeneralPath();
path.moveTo(pDSR.getX(), pDSR.getY());
path.quadTo(pDMR.getX(), pDMR.getY(), pDFS.getX(), pDFS.getY());
// g2.draw(path);
}
}
break;
} // case DOUBLE_XOVER
case RH_XOVER: {
// A, B, C, D end points (left and right)
Point2D vAB = MathUtil.normalize(MathUtil.subtract(pB, pA), railDisplacement);
double dirAB_DEG = MathUtil.computeAngleDEG(vAB);
Point2D vABo = MathUtil.orthogonal(MathUtil.normalize(vAB, railDisplacement));
pAL = MathUtil.subtract(pA, vABo);
pAR = MathUtil.add(pA, vABo);
pBL = MathUtil.subtract(pB, vABo);
pBR = MathUtil.add(pB, vABo);
Point2D vCD = MathUtil.normalize(MathUtil.subtract(pD, pC), railDisplacement);
Point2D vCDo = MathUtil.orthogonal(MathUtil.normalize(vCD, railDisplacement));
pCL = MathUtil.add(pC, vCDo);
pCR = MathUtil.subtract(pC, vCDo);
Point2D pDL = MathUtil.add(pD, vCDo);
Point2D pDR = MathUtil.subtract(pD, vCDo);
// AB and CD mid points
Point2D pABM = MathUtil.midPoint(pA, pB);
Point2D pABL = MathUtil.subtract(pABM, vABo);
Point2D pABR = MathUtil.add(pABM, vABo);
Point2D pCDM = MathUtil.midPoint(pC, pD);
Point2D pCDL = MathUtil.subtract(pCDM, vABo);
Point2D pCDR = MathUtil.add(pCDM, vABo);
// directions
Point2D vAC = MathUtil.normalize(MathUtil.subtract(pCDM, pABM), railDisplacement);
Point2D vACo = MathUtil.orthogonal(MathUtil.normalize(vAC, railDisplacement));
double dirAC_DEG = MathUtil.computeAngleDEG(vAC);
double deltaBAC_DEG = MathUtil.absDiffAngleDEG(dirAB_DEG, dirAC_DEG);
double deltaBAC_RAD = Math.toRadians(deltaBAC_DEG);
// AC mid points
Point2D pACL = MathUtil.subtract(pM, vACo);
Point2D pACR = MathUtil.add(pM, vACo);
// frogs
hypotF = railDisplacement / Math.sin(deltaBAC_RAD / 2.0);
Point2D vF = MathUtil.normalize(MathUtil.add(vAB, vAC), hypotF);
Point2D pABF = MathUtil.add(pABM, vF);
Point2D pCDF = MathUtil.subtract(pCDM, vF);
// frog primes
Point2D pABFP = MathUtil.add(MathUtil.add(pABF, vACo), vACo);
Point2D pCDFP = MathUtil.subtract(MathUtil.subtract(pCDF, vACo), vACo);
// end of switch rails (closed)
Point2D vABF = MathUtil.normalize(vAB, hypotF);
pAP = MathUtil.subtract(pABM, vABF);
pAPL = MathUtil.subtract(pAP, vABo);
pAPR = MathUtil.add(pAP, vABo);
Point2D pCP = MathUtil.add(pCDM, vABF);
Point2D pCPL = MathUtil.add(pCP, vCDo);
Point2D pCPR = MathUtil.subtract(pCP, vCDo);
// end of switch rails (open)
Point2D vS = MathUtil.normalize(vAB, 2.0);
Point2D vSo = MathUtil.orthogonal(vS);
Point2D pASL = MathUtil.add(pAPL, vSo);
// Point2D pASR = MathUtil.subtract(pAPR, vSo);
// Point2D pCSL = MathUtil.add(pCPL, vSo);
Point2D pCSR = MathUtil.subtract(pCPR, vSo);
// end of switch rails (open at frogs)
Point2D pABFS = MathUtil.subtract(pABF, vSo);
// Point2D pABFSP = MathUtil.subtract(pABF, vS);
Point2D pCDFS = MathUtil.add(pCDF, vSo);
// Point2D pCDFSP = MathUtil.add(pCDF, vS);
if (isMain == mainlineA) {
g2.draw(new Line2D.Double(pAL, pABL));
GeneralPath path = new GeneralPath();
path.moveTo(pAR.getX(), pAR.getY());
path.lineTo(pAPR.getX(), pAPR.getY());
path.quadTo(pABR.getX(), pABR.getY(), pABFP.getX(), pABFP.getY());
path.lineTo(pACR.getX(), pACR.getY());
g2.draw(path);
g2.draw(new Line2D.Double(pABF, pACL));
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pAPL.getX(), pAPL.getY());
path.quadTo(pABL.getX(), pABL.getY(), pABF.getX(), pABF.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pASR, pABFSP));
} else { // continuing path
g2.draw(new Line2D.Double(pAPR, pABF));
path = new GeneralPath();
path.moveTo(pASL.getX(), pASL.getY());
path.quadTo(pABL.getX(), pABL.getY(), pABFS.getX(), pABFS.getY());
// g2.draw(path);
}
}
if (isMain == mainlineB) {
g2.draw(new Line2D.Double(pABL, pBL));
g2.draw(new Line2D.Double(pABF, pBR));
}
if (isMain == mainlineC) {
g2.draw(new Line2D.Double(pCR, pCDR));
GeneralPath path = new GeneralPath();
path.moveTo(pCL.getX(), pCL.getY());
path.lineTo(pCPL.getX(), pCPL.getY());
path.quadTo(pCDL.getX(), pCDL.getY(), pCDFP.getX(), pCDFP.getY());
path.lineTo(pACL.getX(), pACL.getY());
g2.draw(path);
g2.draw(new Line2D.Double(pCDF, pACR));
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pCPR.getX(), pCPR.getY());
path.quadTo(pCDR.getX(), pCDR.getY(), pCDF.getX(), pCDF.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pCSL, pCDFSP));
} else { // continuing path
g2.draw(new Line2D.Double(pCPL, pCDF));
path = new GeneralPath();
path.moveTo(pCSR.getX(), pCSR.getY());
path.quadTo(pCDR.getX(), pCDR.getY(), pCDFS.getX(), pCDFS.getY());
// g2.draw(path);
}
}
if (isMain == mainlineD) {
g2.draw(new Line2D.Double(pCDR, pDR));
g2.draw(new Line2D.Double(pCDF, pDL));
}
break;
} // case RH_XOVER
case LH_XOVER: {
// B, A, D, C end points (left and right)
Point2D vBA = MathUtil.normalize(MathUtil.subtract(pA, pB), railDisplacement);
double dirBA_DEG = MathUtil.computeAngleDEG(vBA);
Point2D vBAo = MathUtil.orthogonal(MathUtil.normalize(vBA, railDisplacement));
pBL = MathUtil.add(pB, vBAo);
pBR = MathUtil.subtract(pB, vBAo);
pAL = MathUtil.add(pA, vBAo);
pAR = MathUtil.subtract(pA, vBAo);
Point2D vDC = MathUtil.normalize(MathUtil.subtract(pC, pD), railDisplacement);
Point2D vDCo = MathUtil.orthogonal(MathUtil.normalize(vDC, railDisplacement));
Point2D pDL = MathUtil.subtract(pD, vDCo);
Point2D pDR = MathUtil.add(pD, vDCo);
pCL = MathUtil.subtract(pC, vDCo);
pCR = MathUtil.add(pC, vDCo);
// BA and DC mid points
Point2D pBAM = MathUtil.midPoint(pB, pA);
Point2D pBAL = MathUtil.add(pBAM, vBAo);
Point2D pBAR = MathUtil.subtract(pBAM, vBAo);
Point2D pDCM = MathUtil.midPoint(pD, pC);
Point2D pDCL = MathUtil.add(pDCM, vBAo);
Point2D pDCR = MathUtil.subtract(pDCM, vBAo);
// directions
Point2D vBD = MathUtil.normalize(MathUtil.subtract(pDCM, pBAM), railDisplacement);
Point2D vBDo = MathUtil.orthogonal(MathUtil.normalize(vBD, railDisplacement));
double dirBD_DEG = MathUtil.computeAngleDEG(vBD);
double deltaABD_DEG = MathUtil.absDiffAngleDEG(dirBA_DEG, dirBD_DEG);
double deltaABD_RAD = Math.toRadians(deltaABD_DEG);
// BD mid points
Point2D pBDL = MathUtil.add(pM, vBDo);
Point2D pBDR = MathUtil.subtract(pM, vBDo);
// frogs
hypotF = railDisplacement / Math.sin(deltaABD_RAD / 2.0);
Point2D vF = MathUtil.normalize(MathUtil.add(vBA, vBD), hypotF);
Point2D pBFL = MathUtil.add(pBAM, vF);
Point2D pBF = MathUtil.subtract(pBFL, vBDo);
Point2D pBFR = MathUtil.subtract(pBF, vBDo);
Point2D pDFR = MathUtil.subtract(pDCM, vF);
Point2D pDF = MathUtil.add(pDFR, vBDo);
Point2D pDFL = MathUtil.add(pDF, vBDo);
// end of switch rails (closed)
Point2D vBAF = MathUtil.normalize(vBA, hypotF);
Point2D pBP = MathUtil.subtract(pBAM, vBAF);
Point2D pBPL = MathUtil.add(pBP, vBAo);
Point2D pBPR = MathUtil.subtract(pBP, vBAo);
Point2D pDP = MathUtil.add(pDCM, vBAF);
Point2D pDPL = MathUtil.subtract(pDP, vDCo);
Point2D pDPR = MathUtil.add(pDP, vDCo);
// end of switch rails (open)
Point2D vS = MathUtil.normalize(vBA, 2.0);
Point2D vSo = MathUtil.orthogonal(vS);
Point2D pBSL = MathUtil.subtract(pBPL, vSo);
// Point2D pBSR = MathUtil.add(pBPR, vSo);
// Point2D pDSL = MathUtil.subtract(pDPL, vSo);
Point2D pDSR = MathUtil.add(pDPR, vSo);
// end of switch rails (open at frogs)
Point2D pBAFS = MathUtil.add(pBFL, vSo);
// Point2D pBAFSP = MathUtil.subtract(pBFL, vS);
Point2D pDCFS = MathUtil.subtract(pDFR, vSo);
// Point2D pDCFSP = MathUtil.add(pDFR, vS);
if (isMain == mainlineA) {
g2.draw(new Line2D.Double(pBAL, pAL));
g2.draw(new Line2D.Double(pBFL, pAR));
}
if (isMain == mainlineB) {
g2.draw(new Line2D.Double(pBL, pBAL));
GeneralPath path = new GeneralPath();
path.moveTo(pBR.getX(), pBR.getY());
path.lineTo(pBPR.getX(), pBPR.getY());
path.quadTo(pBAR.getX(), pBAR.getY(), pBFR.getX(), pBFR.getY());
path.lineTo(pBDR.getX(), pBDR.getY());
g2.draw(path);
g2.draw(new Line2D.Double(pBFL, pBDL));
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pBPL.getX(), pBPL.getY());
path.quadTo(pBAL.getX(), pBAL.getY(), pBFL.getX(), pBFL.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pBSR, pBAFSP));
} else { // continuing path
g2.draw(new Line2D.Double(pBPR, pBFL));
path = new GeneralPath();
path.moveTo(pBSL.getX(), pBSL.getY());
path.quadTo(pBAL.getX(), pBAL.getY(), pBAFS.getX(), pBAFS.getY());
// g2.draw(path);
}
}
if (isMain == mainlineC) {
g2.draw(new Line2D.Double(pDCR, pCR));
g2.draw(new Line2D.Double(pDFR, pCL));
}
if (isMain == mainlineD) {
g2.draw(new Line2D.Double(pDR, pDCR));
GeneralPath path = new GeneralPath();
path.moveTo(pDL.getX(), pDL.getY());
path.lineTo(pDPL.getX(), pDPL.getY());
path.quadTo(pDCL.getX(), pDCL.getY(), pDFL.getX(), pDFL.getY());
path.lineTo(pBDL.getX(), pBDL.getY());
g2.draw(path);
g2.draw(new Line2D.Double(pDFR, pBDR));
if (state != Turnout.CLOSED) { // unknown or diverting path
path = new GeneralPath();
path.moveTo(pDPR.getX(), pDPR.getY());
path.quadTo(pDCR.getX(), pDCR.getY(), pDFR.getX(), pDFR.getY());
g2.draw(path);
// g2.draw(new Line2D.Double(pDSL, pDCFSP));
} else { // continuing path
g2.draw(new Line2D.Double(pDPL, pDFR));
path = new GeneralPath();
path.moveTo(pDSR.getX(), pDSR.getY());
path.quadTo(pDCR.getX(), pDCR.getY(), pDCFS.getX(), pDCFS.getY());
// g2.draw(path);
}
}
break;
} // case LH_XOVER
case SINGLE_SLIP:
case DOUBLE_SLIP: {
log.error("{}.draw2(...); slips should be being drawn by LayoutSlip sub-class", getName());
break;
}
default: {
// this should never happen... but...
log.error("{}.draw2(...); Unknown turnout type {}", getName(), type);
break;
}
}
} // draw2
/**
* {@inheritDoc}
*/
@Override
protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) {
if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_A))
&& (getConnectA() == null)) {
g2.fill(trackControlCircleAt(getCoordsA()));
}
if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_B))
&& (getConnectB() == null)) {
g2.fill(trackControlCircleAt(getCoordsB()));
}
if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_C))
&& (getConnectC() == null)) {
g2.fill(trackControlCircleAt(getCoordsC()));
}
if (isTurnoutTypeXover()) {
if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_D))
&& (getConnectD() == null)) {
g2.fill(trackControlCircleAt(getCoordsD()));
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void drawTurnoutControls(Graphics2D g2) {
if (!isDisabled() && !(isDisabledWhenOccupied() && isOccupied())) {
Color foregroundColor = g2.getColor();
if (getState() != Turnout.CLOSED) {
// then switch to background (thrown) color
g2.setColor(g2.getBackground());
}
if (layoutEditor.isTurnoutFillControlCircles()) {
g2.fill(trackControlCircleAt(getCoordsCenter()));
} else {
g2.draw(trackControlCircleAt(getCoordsCenter()));
}
if (getState() != Turnout.CLOSED) {
// then restore foreground color
g2.setColor(foregroundColor);
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void drawEditControls(Graphics2D g2) {
Point2D pt = getCoordsA();
if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
if (getConnectA() == null) {
g2.setColor(Color.magenta);
} else {
g2.setColor(Color.blue);
}
} else {
if (getConnectA() == null) {
g2.setColor(Color.red);
} else {
g2.setColor(Color.green);
}
}
g2.draw(layoutEditor.layoutEditorControlRectAt(pt));
pt = getCoordsB();
if (getConnectB() == null) {
g2.setColor(Color.red);
} else {
g2.setColor(Color.green);
}
g2.draw(layoutEditor.layoutEditorControlRectAt(pt));
pt = getCoordsC();
if (getConnectC() == null) {
g2.setColor(Color.red);
} else {
g2.setColor(Color.green);
}
g2.draw(layoutEditor.layoutEditorControlRectAt(pt));
if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
pt = getCoordsD();
if (getConnectD() == null) {
g2.setColor(Color.red);
} else {
g2.setColor(Color.green);
}
g2.draw(layoutEditor.layoutEditorControlRectAt(pt));
}
}
/*
* Used by ConnectivityUtil to determine the turnout state necessary to get
* from prevLayoutBlock ==> currLayoutBlock ==> nextLayoutBlock
*/
protected int getConnectivityStateForLayoutBlocks(
LayoutBlock currLayoutBlock,
LayoutBlock prevLayoutBlock,
LayoutBlock nextLayoutBlock,
boolean suppress) {
return turnout.getConnectivityStateForLayoutBlocks(currLayoutBlock,
prevLayoutBlock,
nextLayoutBlock,
suppress);
}
/**
* {@inheritDoc}
*/
// TODO: on the cross-overs, check the internal boundary details.
@Override
public void reCheckBlockBoundary() {
turnout.reCheckBlockBoundary();
}
/**
* {@inheritDoc}
*/
@Override
protected List<LayoutConnectivity> getLayoutConnectivity() {
return turnout.getLayoutConnectivity();
}
/**
* {@inheritDoc}
*/
@Override
public @Nonnull
List<HitPointType> checkForFreeConnections() {
return turnout.checkForFreeConnections();
}
/**
* {@inheritDoc}
*/
@Override
public boolean checkForUnAssignedBlocks() {
// because getLayoutBlock[BCD] will return block [A] if they're null
// we only need to test block [A]
return turnout.checkForUnAssignedBlocks();
}
/**
* {@inheritDoc}
*/
@Override
public void checkForNonContiguousBlocks(
@Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
turnout.checkForNonContiguousBlocks(blockNamesToTrackNameSetsMap);
}
/**
* {@inheritDoc}
*/
@Override
public void collectContiguousTracksNamesInBlockNamed(
@Nonnull String blockName,
@Nonnull Set<String> TrackNameSet) {
turnout.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
}
/**
* {@inheritDoc}
*/
@Override
public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
turnout.setAllLayoutBlocks(layoutBlock);
}
private static class AbstractActionImpl extends AbstractAction {
private final String blockName;
private final LayoutBlock layoutBlock;
public AbstractActionImpl(String name, String blockName, LayoutBlock layoutBlock) {
super(name);
this.blockName = blockName;
this.layoutBlock = layoutBlock;
}
@Override
public void actionPerformed(ActionEvent e) {
AbstractAction routeTableAction = new LayoutBlockRouteTableAction(blockName, layoutBlock);
routeTableAction.actionPerformed(e);
}
}
private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurnoutView.class);
}