package fr.orsay.lri.varna.applications.templateEditor; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Shape; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate; import fr.orsay.lri.varna.models.rna.RNA; import fr.orsay.lri.varna.models.templates.RNATemplate; import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition; import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix; import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint; public class Helix extends GraphicalTemplateElement{ RNATemplateHelix _h; public Helix(double x, double y, RNATemplate tmp, List<GraphicalTemplateElement> existingRNAElements) { this(x,y,getNextAutomaticCaption(existingRNAElements),tmp); } public Helix(double x, double y, String cap, RNATemplate tmp) { _h = tmp.new RNATemplateHelix(cap); _h.setStartPosition(new Point2D.Double(x,y)); _h.setEndPosition(new Point2D.Double(x,y)); _h.setLength(1); _h.setCaption(cap); } public Helix(RNATemplateHelix templateHelix) { _h = templateHelix; } private static String getNextAutomaticCaption(List<GraphicalTemplateElement> existingRNAElements) { // Find which captions are already used Set<String> captions = new HashSet<String>(); for (GraphicalTemplateElement element: existingRNAElements) { if (element instanceof Helix) { Helix helix = (Helix) element; if (helix.getCaption() != null) { captions.add(helix.getCaption()); } } } // Find a non-conflicting name for this helix for (int i=1;;i++) { String candidateCaption = "H" + i; if (! captions.contains(candidateCaption)) { return candidateCaption; } } } public void toggleFlipped() { _h.setFlipped(!_h.isFlipped()); updateAttachedUnpairedRegions(); } /** * When an helix is moved/resized/etc... it is necessary to update * the positions of endpoints from unpaired regions that are attached * to the helix. This function updates the endpoints positions of * attached unpaired regions. */ public void updateAttachedUnpairedRegions() { for (RelativePosition rpos: getConnectedEdges()) { Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(rpos); if (c != null && c.second instanceof UnpairedRegion) { UnpairedRegion unpairedRegion = (UnpairedRegion) c.second; Point2D.Double pos = getEdgePosition(rpos); if (c.first == RelativePosition.RP_CONNECT_START5) { unpairedRegion.setEdge5(pos); } else if (c.first == RelativePosition.RP_CONNECT_END3) { unpairedRegion.setEdge3(pos); } } } } public double getPosX() { return _h.getStartPosition().x; } public String getCaption() { return _h.getCaption(); } public double getPosY() { return _h.getStartPosition().y; } public RNATemplateHelix getTemplateElement() { return _h; } public void setX(double x) { _h.getStartPosition().x = x; } public void setY(double y) { _h.getStartPosition().y = y; } public void setPos(Point2D.Double p) { _h.setStartPosition(p); updateLength(); } public void setPos(double x, double y) { setPos(new Point2D.Double(x,y)); } public Point2D.Double getPos() { return _h.getStartPosition(); } public void moveCenter(double x, double y) { Point2D.Double center = new Point2D.Double((_h.getStartPosition().x+_h.getEndPosition().x)/2.0,(_h.getStartPosition().y+_h.getEndPosition().y)/2.0); double dx = x-center.x; double dy = y-center.y; _h.setStartPosition(new Point2D.Double(_h.getStartPosition().x+dx,_h.getStartPosition().y+dy)); _h.setEndPosition(new Point2D.Double(_h.getEndPosition().x+dx,_h.getEndPosition().y+dy)); } public void setExtent(double x, double y) { setExtent(new Point2D.Double(x,y)); } private void updateLength() { _h.setLength(getNbBP()); } public void setExtent(Point2D.Double p) { _h.setEndPosition(p); updateLength(); } public double getExtentX() { return _h.getEndPosition().x; } public Point2D.Double getExtent() { return _h.getEndPosition(); } public double getExtentY() { return _h.getEndPosition().y; } public static final double BASE_PAIR_DISTANCE = RNA.BASE_PAIR_DISTANCE; public static final double LOOP_DISTANCE = RNA.LOOP_DISTANCE; public static final double SELECTION_RADIUS = 15.0; public Point2D.Double getAbsStart5() { double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition())); double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition())); double nx = dy; double ny = -dx; Point2D.Double start5 = new Point2D.Double((getPosX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()-Helix.BASE_PAIR_DISTANCE*ny/2.0)); return start5; } public Point2D.Double getAbsStart3() { double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition())); double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition())); double nx = dy; double ny = -dx; Point2D.Double start3 = new Point2D.Double((getPosX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()+Helix.BASE_PAIR_DISTANCE*ny/2.0)); return start3; } public Point2D.Double getAbsEnd5() { double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition())); double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition())); double nx = dy; double ny = -dx; Point2D.Double end5 = new Point2D.Double((getExtentX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()-Helix.BASE_PAIR_DISTANCE*ny/2.0)); return end5; } public Point2D.Double getAbsEnd3() { double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition())); double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition())); double nx = dy; double ny = -dx; Point2D.Double end3 = new Point2D.Double((getExtentX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()+Helix.BASE_PAIR_DISTANCE*ny/2.0)); return end3; } public Point2D.Double getStart5() { if (_h.isFlipped()) return getAbsStart3(); else return getAbsStart5(); } public Point2D.Double getStart3() { if (_h.isFlipped()) return getAbsStart5(); else return getAbsStart3(); } public Point2D.Double getEnd5() { if (_h.isFlipped()) return getAbsEnd3(); else return getAbsEnd5(); } public Point2D.Double getEnd3() { if (_h.isFlipped()) return getAbsEnd5(); else return getAbsEnd3(); } public Polygon getBoundingPolygon() { double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition())); double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition())); double nx = dy; double ny = -dx; Point2D.Double start5 = new Point2D.Double((getPosX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()+Helix.BASE_PAIR_DISTANCE*ny/2.0)); Point2D.Double end5 = new Point2D.Double((getExtentX()+Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()+Helix.BASE_PAIR_DISTANCE*ny/2.0)); Point2D.Double start3 = new Point2D.Double((getPosX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getPosY()-Helix.BASE_PAIR_DISTANCE*ny/2.0)); Point2D.Double end3 = new Point2D.Double((getExtentX()-Helix.BASE_PAIR_DISTANCE*nx/2.0),(getExtentY()-Helix.BASE_PAIR_DISTANCE*ny/2.0)); Polygon p = new Polygon(); p.addPoint((int)start5.x, (int)start5.y); p.addPoint((int)end5.x, (int)end5.y); p.addPoint((int)end3.x, (int)end3.y); p.addPoint((int)start3.x, (int)start3.y); return p; } public Point2D.Double getCenter() { return new Point2D.Double((int)((_h.getStartPosition().x+_h.getEndPosition().x)/2.0), (int)((_h.getStartPosition().y+_h.getEndPosition().y)/2.0)); } public Point2D.Double getCenterEditStart() { double dist = _h.getStartPosition().distance(_h.getEndPosition()); double dx = (_h.getEndPosition().x-_h.getStartPosition().x)/(dist); double dy = (_h.getEndPosition().y-_h.getStartPosition().y)/(dist); return new Point2D.Double((int)(_h.getStartPosition().x+(dist-10.0)*dx), (int)(_h.getStartPosition().y+(dist-10.0)*dy)); } public Point2D.Double getCenterEditEnd() { double dist = _h.getStartPosition().distance(_h.getEndPosition()); double dx = (_h.getEndPosition().x-_h.getStartPosition().x)/(dist); double dy = (_h.getEndPosition().y-_h.getStartPosition().y)/(dist); return new Point2D.Double((int)(_h.getStartPosition().x+(10.0)*dx), (int)(_h.getStartPosition().y+(10.0)*dy)); } public Shape getSelectionBox() { double dx = (_h.getStartPosition().x-_h.getEndPosition().x)/(_h.getStartPosition().distance(_h.getEndPosition())); double dy = (_h.getStartPosition().y-_h.getEndPosition().y)/(_h.getStartPosition().distance(_h.getEndPosition())); double nx = dy; double ny = -dx; Polygon hbox = getBoundingPolygon(); Polygon p = new Polygon(); Point2D.Double start5 = new Point2D.Double(hbox.xpoints[0]+SELECTION_RADIUS*(dx+nx),hbox.ypoints[0]+SELECTION_RADIUS*(dy+ny)); Point2D.Double end5 = new Point2D.Double(hbox.xpoints[1]+SELECTION_RADIUS*(-dx+nx),hbox.ypoints[1]+SELECTION_RADIUS*(-dy+ny)); Point2D.Double end3 = new Point2D.Double(hbox.xpoints[2]+SELECTION_RADIUS*(-dx-nx),hbox.ypoints[2]+SELECTION_RADIUS*(-dy-ny));; Point2D.Double start3 = new Point2D.Double(hbox.xpoints[3]+SELECTION_RADIUS*(dx-nx),hbox.ypoints[3]+SELECTION_RADIUS*(dy-ny));; p.addPoint((int)start5.x, (int)start5.y); p.addPoint((int)end5.x, (int)end5.y); p.addPoint((int)end3.x, (int)end3.y); p.addPoint((int)start3.x, (int)start3.y); return p; } public Shape getArea() { return getSelectionBox(); } public static final double EDIT_RADIUS = 10.0; public static final double MOVE_RADIUS = 13.0; public static final double BASE_RADIUS = 3.0; public static final double EDGE_BASE_RADIUS = 7.0; public RelativePosition getRelativePosition(double x, double y) { Point2D.Double current = new Point2D.Double(x,y); Shape p = getSelectionBox(); if (p.contains(current)) { if (getCenterEditStart().distance(current)<EDIT_RADIUS) { return RelativePosition.RP_EDIT_START; } else if (getCenterEditEnd().distance(current)<EDIT_RADIUS) { return RelativePosition.RP_EDIT_END; } else if (getCenter().distance(current)<MOVE_RADIUS) { return RelativePosition.RP_INNER_MOVE; } else if (getEnd3().distance(current)<EDGE_BASE_RADIUS) { return RelativePosition.RP_CONNECT_END3; } else if (getEnd5().distance(current)<EDGE_BASE_RADIUS) { return RelativePosition.RP_CONNECT_END5; } else if (getStart3().distance(current)<EDGE_BASE_RADIUS) { return RelativePosition.RP_CONNECT_START3; } else if (getStart5().distance(current)<EDGE_BASE_RADIUS) { return RelativePosition.RP_CONNECT_START5; } return RelativePosition.RP_INNER_GENERAL; } else return RelativePosition.RP_OUTER; } public RelativePosition getClosestEdge(double x, double y) { RelativePosition result = RelativePosition.RP_OUTER; double dist = Double.MAX_VALUE; Point2D.Double current = new Point2D.Double(x,y); double dcand = getStart5().distance(current); if (dcand<dist) { dist = dcand; result = RelativePosition.RP_CONNECT_START5; } dcand = getStart3().distance(current); if (dcand<dist) { dist = dcand; result = RelativePosition.RP_CONNECT_START3; } dcand = getEnd5().distance(current); if (dcand<dist) { dist = dcand; result = RelativePosition.RP_CONNECT_END5; } dcand = getEnd3().distance(current); if (dcand<dist) { dist = dcand; result = RelativePosition.RP_CONNECT_END3; } return result; } public Point2D.Double getEdgePosition(Helix.RelativePosition edge) { switch (edge) { case RP_CONNECT_END3: return getEnd3(); case RP_CONNECT_END5: return getEnd5(); case RP_CONNECT_START5: return getStart5(); case RP_CONNECT_START3: return getStart3(); case RP_EDIT_START: return getPos(); case RP_EDIT_END: return getExtent(); case RP_INNER_MOVE: return getCenter(); } return getCenter(); } public RelativePosition getConnectedEdge(RelativePosition edge) { switch (edge) { case RP_CONNECT_END3: return RelativePosition.RP_CONNECT_START3; case RP_CONNECT_END5: return RelativePosition.RP_CONNECT_START5; case RP_CONNECT_START5: return RelativePosition.RP_CONNECT_END5; case RP_CONNECT_START3: return RelativePosition.RP_CONNECT_END3; } return RelativePosition.RP_OUTER; } public boolean isAnchored5Start() { return (_h.getIn1().getOtherElement()!=null); } public boolean isAnchored5End() { return (_h.getOut1().getOtherElement()!=null); } public boolean isAnchored3Start() { return (_h.getOut2().getOtherElement()!=null); } public boolean isAnchored3End() { return (_h.getIn2().getOtherElement()!=null); } public int getNbBP() { Point2D.Double pos = getPos(); Point2D.Double extent = getExtent(); double helLength = pos.distance(extent); return Math.max((int)Math.round(helLength/Helix.LOOP_DISTANCE) + 1, 2); } public void draw(Graphics2D g2d,boolean isSelected) { g2d.setStroke(_solidStroke); Point2D.Double pos = getPos(); Point2D.Double extent = getExtent(); double dx = (pos.x-extent.x)/pos.distance(extent); double dy = (pos.y-extent.y)/pos.distance(extent); double nx = Helix.BASE_PAIR_DISTANCE*dy/2.0; double ny = -Helix.BASE_PAIR_DISTANCE*dx/2.0; Point2D.Double start5 = getStart5(); Point2D.Double end5 = getEnd5(); Point2D.Double start3 = getStart3(); Point2D.Double end3 = getEnd3(); for (RelativePosition e:this.getConnectedEdges()) { g2d.setStroke(_solidStroke); g2d.setColor(BACKBONE_COLOR); Point2D.Double p1 = this.getEdgePosition(e); Point2D.Double p2 = this.getEdgePosition(getConnectedEdge(e)); if (_mainColors.containsKey(e)) { g2d.setColor(_mainColors.get(e)); g2d.setStroke(this._boldStroke); } g2d.drawLine((int)p1.x,(int)p1.y,(int)(p1.x+p2.x)/2,(int)(p1.y+p2.y)/2); } g2d.setColor(NUMBER_COLOR); double captionx = (_h.isFlipped()?-1.0:1.0)*1.5*nx+(start3.x+end3.x)/2.0; double captiony = (_h.isFlipped()?-1.0:1.0)*1.5*ny+(start3.y+end3.y)/2.0; drawStringCentered(g2d, getCaption(),captionx,captiony); int nbBasePairs = _h.getLength(); g2d.setStroke(_solidStroke); for (int i=0;i<nbBasePairs;i++) { g2d.setColor(BASE_PAIR_COLOR); Point2D.Double p5 = new Point2D.Double( (i*start5.x+(nbBasePairs-1-i)*end5.x)/(nbBasePairs-1), (i*start5.y+(nbBasePairs-1-i)*end5.y)/(nbBasePairs-1)); Point2D.Double p3 = new Point2D.Double( (i*start3.x+(nbBasePairs-1-i)*end3.x)/(nbBasePairs-1), (i*start3.y+(nbBasePairs-1-i)*end3.y)/(nbBasePairs-1)); g2d.drawLine((int)p3.x,(int)p3.y,(int)p5.x,(int)p5.y); if (i==0) { if (isAnchored5End()) { drawMagnet(g2d, p5); } else { drawAnchor3(g2d, p5); } if (isAnchored3End()) { drawMagnet(g2d, p3); } else { drawAnchor5(g2d, p3); } } else if (i==nbBasePairs-1) { if (isAnchored5Start()) { drawMagnet(g2d, p5); } else { drawAnchor5(g2d, p5); } if (isAnchored3Start()) { drawMagnet(g2d, p3); } else { drawAnchor3(g2d, p3); } } else { drawBase(g2d, p3); drawBase(g2d, p5); } } if (isSelected) { nx = dy; ny = -dx; Shape p = getSelectionBox(); g2d.setColor(BACKBONE_COLOR); g2d.setStroke(_dashedStroke); g2d.draw(p); Point2D.Double center = getCenter(); g2d.setStroke(_solidStroke); drawMove(g2d,center); drawEditStart(g2d,this,-dx,-dy,nx,ny); drawEditEnd(g2d,this,-dx,-dy,nx,ny); } } public void translate(double x, double y) { Point2D.Double pos = getPos(); Point2D.Double extent = getExtent(); setPos(pos.x+x,pos.y+y); setExtent(extent.x+x,extent.y+y); } public RNATemplateHelix getHelix() { return _h; } public EdgeEndPoint getEndPoint(RelativePosition r) { switch(r) { case RP_CONNECT_START5: return _h.getIn1(); case RP_CONNECT_START3: return _h.getOut2(); case RP_CONNECT_END3: return _h.getIn2(); case RP_CONNECT_END5: return _h.getOut1(); } return null; } public boolean isIn(RelativePosition r) { switch(r) { case RP_CONNECT_START5: return true; case RP_CONNECT_START3: return false; case RP_CONNECT_END3: return true; case RP_CONNECT_END5: return false; } return true; } public void attach(GraphicalTemplateElement e, RelativePosition edgeOrig, RelativePosition edgeDest) throws ExceptionInvalidRNATemplate { super.attach(e,edgeOrig,edgeDest); EdgeEndPoint e1 = this.getEndPoint(edgeOrig); EdgeEndPoint e2 = e.getEndPoint(edgeDest); boolean parity1 = this.isIn(edgeOrig); boolean parity2 = e.isIn(edgeDest); if ((e1!=null)&&(e2!=null)&&(parity1!=parity2)) { e1.disconnect(); e2.disconnect(); e1.connectTo(e2); } } public void detach(RelativePosition edge) { // If the underlying template element is still connected, disconnect it if (getEndPoint(edge).isConnected()) { getEndPoint(edge).disconnect(); } // Call the parent class detach function, which will also take care to disconnect this other endpoint of this edge super.detach(edge); } public void setEdgePosition(RelativePosition edge, java.awt.geom.Point2D.Double pos) { switch (edge) { case RP_EDIT_START: setPos(pos); break; case RP_EDIT_END: setExtent(pos); break; case RP_INNER_MOVE: moveCenter(pos.x,pos.y); break; } updateAttachedUnpairedRegions(); } public ArrayList<RelativePosition> getConnectedEdges() { ArrayList<RelativePosition> result = new ArrayList<RelativePosition>(); result.add(RelativePosition.RP_CONNECT_START5); result.add(RelativePosition.RP_CONNECT_START3); result.add(RelativePosition.RP_CONNECT_END5); result.add(RelativePosition.RP_CONNECT_END3); return result; } public String toString() { return "Helix " + getCaption(); } public RelativePosition relativePositionFromEdgeEndPointPosition( EdgeEndPointPosition pos) { switch (pos) { case IN1: return RelativePosition.RP_CONNECT_START5; case OUT1: return RelativePosition.RP_CONNECT_END5; case IN2: return RelativePosition.RP_CONNECT_END3; case OUT2: return RelativePosition.RP_CONNECT_START3; default: return null; } } }