001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.bio.gui.sequence;
023
024import java.awt.Color;
025import java.awt.Graphics2D;
026import java.awt.Paint;
027import java.awt.Shape;
028import java.awt.event.MouseEvent;
029import java.awt.geom.Area;
030import java.awt.geom.GeneralPath;
031import java.awt.geom.Rectangle2D;
032
033import org.biojava.bio.seq.Feature;
034import org.biojava.bio.seq.FeatureHolder;
035import org.biojava.bio.seq.StrandedFeature;
036import org.biojava.bio.symbol.Location;
037import org.biojava.utils.AbstractChangeable;
038import org.biojava.utils.ChangeEvent;
039import org.biojava.utils.ChangeSupport;
040import org.biojava.utils.ChangeType;
041import org.biojava.utils.ChangeVetoException;
042
043/**
044 * @author Matthew Pocock
045 * @author Keith James
046 * @author Thomas Down
047 */
048public class BasicFeatureRenderer
049extends AbstractChangeable
050implements FeatureRenderer {
051  public static final ChangeType FILL = new ChangeType(
052    "The fill paint has changed",
053    "org.biojava.bio.gui.sequence.BasicFeatureRenderer",
054    "FILL",
055    SequenceRenderContext.REPAINT
056  );
057  
058  public static final ChangeType OUTLINE = new ChangeType(
059    "The outline paint has changed",
060    "org.biojava.bio.gui.sequence.BasicFeatureRenderer",
061    "OUTLINE",
062    SequenceRenderContext.REPAINT
063  );
064  
065  public static final ChangeType SIZE = new ChangeType(
066    "The size of the arrow has changed",
067    "org.biojava.bio.gui.sequence.BasicFeatureRenderer",
068    "SIZE",
069    SequenceRenderContext.LAYOUT
070  );
071  
072  public static final ChangeType SCOOP = new ChangeType(
073    "The scoop of the arrow has changed",
074    "org.biojava.bio.gui.sequence.BasicFeatureRenderer",
075    "SCOOP",
076    SequenceRenderContext.REPAINT
077  );
078
079  private Paint fill;
080  private Paint outline;
081  private double arrowSize = 15.0;
082  private double arrowScoop = 4.0;
083
084  public BasicFeatureRenderer() {
085    fill = Color.red;
086    outline = Color.black;
087  }
088
089  public void setFill(Paint p)
090  throws ChangeVetoException {
091    if(hasListeners()) {
092      ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);
093      synchronized(cs) {
094        ChangeEvent ce = new ChangeEvent(
095          this, FILL, p, fill
096        );
097        cs.firePreChangeEvent(ce);
098        fill = p;
099        cs.firePostChangeEvent(ce);
100      }
101    } else {
102      fill = p;
103    }
104  }
105  
106  public Paint getFill() {
107    return fill;
108  }
109  
110  public void setOutline(Paint p)
111  throws ChangeVetoException {
112    if(hasListeners()) {
113      ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);
114      synchronized(cs) {
115        ChangeEvent ce = new ChangeEvent(
116          this, OUTLINE, p, outline
117        );
118        cs.firePreChangeEvent(ce);
119        outline = p;
120        cs.firePostChangeEvent(ce);
121      }
122    } else {
123      outline = p;
124    }
125  }
126  
127  public Paint getOutline() {
128    return outline;
129  }
130  
131  public void setArrowSize(double arrowSize)
132  throws ChangeVetoException {
133    if(hasListeners()) {
134      ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
135      synchronized(cs) {
136        ChangeEvent ce = new ChangeEvent(
137          this, SequenceRenderContext.LAYOUT,
138          null, null, new ChangeEvent(
139            this, SIZE, new Double(this.arrowSize), new Double(arrowSize)
140          )
141        );
142        cs.firePreChangeEvent(ce);
143        this.arrowSize = arrowSize;
144        cs.firePostChangeEvent(ce);
145      }
146    } else {
147      this.arrowSize = arrowSize;
148    }
149  }
150  
151  public double getArrowSize() {
152    return arrowSize;
153  }
154  
155  public void setArrowScoop(double arrowScoop)
156  throws ChangeVetoException {
157    if(hasListeners()) {
158      ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
159      synchronized(cs) {
160        ChangeEvent ce = new ChangeEvent(
161          this, SequenceRenderContext.LAYOUT,
162          null, null, new ChangeEvent(
163            this, SIZE, new Double(this.arrowSize), new Double(arrowSize)
164          )
165        );
166        cs.firePreChangeEvent(ce);
167        this.arrowScoop = arrowScoop;
168        cs.firePostChangeEvent(ce);
169      }
170    } else {
171      this.arrowScoop = arrowScoop;
172    }
173  }
174    
175  public double getArrowScoop() {
176    return arrowScoop;
177  }
178  
179  public void renderFeature(
180    Graphics2D g,
181    Feature f, 
182    SequenceRenderContext src
183  ) {
184    Shape s = null;
185    Location loc = f.getLocation();
186    float depth = (float) (arrowSize + 2.0 * arrowScoop);
187
188    double minD, maxD;
189    if (src.getScale() > 1.0) {
190        minD = src.sequenceToGraphics(loc.getMin());
191        maxD = src.sequenceToGraphics(loc.getMax() + 1) - 1.0;
192    } else {
193        minD = src.sequenceToGraphics(loc.getMin());
194        maxD = src.sequenceToGraphics(loc.getMax());
195    }
196    float min = (float) minD;
197    float max = (float) maxD;
198
199    float minBounds = (float) src.sequenceToGraphics(src.getRange().getMin() - 1);
200    float maxBounds = (float) src.sequenceToGraphics(src.getRange().getMax() + 1);
201    Shape bounds;
202    if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
203        bounds = new Rectangle2D.Double(minBounds, 0, maxBounds - minBounds, depth);
204    } else {
205        bounds = new Rectangle2D.Double(0, minBounds, depth, maxBounds - minBounds);
206    }
207
208    // System.err.println("Drawing feature " + f.getType() + " min= " + min + "      max=" + max);
209
210    
211    if( (max - min) >= arrowSize) {
212      if (f instanceof StrandedFeature) {
213        StrandedFeature.Strand strand = ((StrandedFeature) f).getStrand();
214        if(src.getDirection() == SequenceRenderContext.HORIZONTAL) {
215          float minY = 0.0f;
216          float maxY = depth;
217          float minYS = minY + (float) arrowScoop;
218          float maxYS = maxY - (float) arrowScoop;
219          float midY = (minY + maxY) * 0.5f;
220          float minX = min;
221          float maxX = max;
222          if(strand == StrandedFeature.POSITIVE) {
223            float midX = maxX - (float) arrowSize;
224            GeneralPath path = new GeneralPath();
225            path.moveTo(minX, minYS);
226            path.lineTo(midX, minYS);
227            path.lineTo(midX, minY);
228            path.lineTo(maxX, midY);
229            path.lineTo(midX, maxY);
230            path.lineTo(midX, maxYS);
231            path.lineTo(minX, maxYS);
232            path.closePath();
233            s = path;
234          } else if(strand == StrandedFeature.NEGATIVE) {
235            float midX = minX + (float) arrowSize;
236            GeneralPath path = new GeneralPath();
237            path.moveTo(maxX, minYS);
238            path.lineTo(midX, minYS);
239            path.lineTo(midX, minY);
240            path.lineTo(minX, midY);
241            path.lineTo(midX, maxY);
242            path.lineTo(midX, maxYS);
243            path.lineTo(maxX, maxYS);
244            path.closePath();
245            s = path;
246          }
247        } else { // vertical
248          float minX = 0.0f;
249          float maxX = depth;
250          float minXS = minX + (float) arrowScoop;
251          float maxXS = maxX - (float) arrowScoop;
252          float midX = (minX + maxX) * 0.5f;
253          float minY = min;
254          float maxY = max;
255          if(strand == StrandedFeature.POSITIVE) {
256            float midY = maxY - (float) arrowSize;
257            GeneralPath path = new GeneralPath();
258            path.moveTo(minXS, minY);
259            path.lineTo(minXS, midY);
260            path.lineTo(minX, midY);
261            path.lineTo(midX, maxY);
262            path.lineTo(maxX, midY);
263            path.lineTo(maxXS, midY);
264            path.lineTo(maxXS, minY);
265            path.closePath();
266            s = path;
267          } else if(strand == StrandedFeature.NEGATIVE) {
268            float midY = minY + (float) arrowSize;
269            GeneralPath path = new GeneralPath();
270            path.moveTo(minXS, maxY);
271            path.lineTo(minXS, midY);
272            path.lineTo(minX, midY);
273            path.lineTo(midX, minY);
274            path.lineTo(maxX, midY);
275            path.lineTo(maxXS, midY);
276            path.lineTo(maxXS, maxY);
277            path.closePath();
278            s = path;
279          }
280        }
281      }
282    }
283    if(s == null) {
284      if(src.getDirection() == SequenceRenderContext.HORIZONTAL) {
285        s = new Rectangle2D.Double(min, 0, Math.max(1.0, max-min), 2.0*arrowScoop + arrowSize);
286      } else {
287        s = new Rectangle2D.Double(0, min, 2.0*arrowScoop + arrowSize, Math.max(1.0, max-min));
288      }
289    }
290    
291    if (!bounds.contains(s.getBounds())) {
292        //      System.err.println("Edgeclipping");
293
294        s = new Area(s);
295        ((Area) s).intersect(new Area(bounds));
296    }
297
298    if(fill != null) {
299      g.setPaint(fill);
300      g.fill(s);
301    }
302    if ( (outline != null) && ( (maxD - minD) > 4.0) ) {
303      g.setPaint(outline);
304      g.draw(s);
305    } else {
306        //      System.err.println("Not drawing outline...");
307    }
308  }
309  
310  public double getDepth(SequenceRenderContext src) {
311    return arrowSize + 2.0 * arrowScoop + 1.0;
312  }
313  
314  public FeatureHolder processMouseEvent(
315    FeatureHolder hits,
316    SequenceRenderContext src,
317    MouseEvent me
318  ) {
319    return hits;
320  }
321}