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.Graphics2D;
025import java.awt.event.MouseEvent;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Set;
032
033import org.biojava.bio.BioError;
034import org.biojava.bio.seq.Feature;
035import org.biojava.bio.seq.FeatureFilter;
036import org.biojava.bio.seq.FeatureHolder;
037import org.biojava.bio.seq.FilterUtils;
038import org.biojava.bio.seq.SimpleFeatureHolder;
039import org.biojava.bio.symbol.Location;
040import org.biojava.bio.symbol.RangeLocation;
041import org.biojava.utils.AssertionFailure;
042import org.biojava.utils.ChangeEvent;
043import org.biojava.utils.ChangeListener;
044import org.biojava.utils.ChangeSupport;
045import org.biojava.utils.ChangeType;
046import org.biojava.utils.ChangeVetoException;
047import org.biojava.utils.Changeable;
048import org.biojava.utils.cache.CacheMap;
049import org.biojava.utils.cache.FixedSizeMap;
050
051/**
052 * @author Matthew Pocock
053 */
054public class BumpedRenderer
055extends SequenceRendererWrapper {
056  private int leadingPixles;
057  private int trailingPixles;
058
059  public BumpedRenderer() {}
060
061  public BumpedRenderer(SequenceRenderer renderer) {
062    super(renderer);
063  }
064
065  public BumpedRenderer(SequenceRenderer renderer, int leading, int trailing) {
066    super(renderer);
067    this.leadingPixles = leading;
068    this.trailingPixles = trailing;
069  }
070
071  public int getLeadingPixles() {
072    return leadingPixles;
073  }
074
075  public void setLeadingPixles(int leading) {
076    this.leadingPixles = leading;
077  }
078
079  public int getTrailingPixles() {
080    return trailingPixles;
081  }
082
083  public void setTrailingPixles(int trailing) {
084    this.trailingPixles = trailing;
085  }
086
087  protected boolean hasListeners() {
088    return super.hasListeners();
089  }
090
091  protected ChangeSupport getChangeSupport(ChangeType ct) {
092    return super.getChangeSupport(ct);
093  }
094
095  public double getDepth(SequenceRenderContext src) {
096    List layers = layer(src);
097    return LayeredRenderer.INSTANCE.getDepth(
098      layers,
099      Collections.nCopies(layers.size(), getRenderer())
100    );
101  }
102
103  public double getMinimumLeader(SequenceRenderContext src) {
104    List layers = layer(src);
105    return LayeredRenderer.INSTANCE.getMinimumLeader(
106      layers,
107      Collections.nCopies(layers.size(), getRenderer())
108    );
109  }
110
111  public double getMinimumTrailer(SequenceRenderContext src) {
112    List layers = layer(src);
113    return LayeredRenderer.INSTANCE.getMinimumTrailer(
114      layers,
115      Collections.nCopies(layers.size(), getRenderer())
116    );
117  }
118
119  public void paint(
120    Graphics2D g,
121    SequenceRenderContext src
122  ) {
123    List layers = layer(src);
124    LayeredRenderer.INSTANCE.paint(
125      g,
126      layers,
127      Collections.nCopies(layers.size(), getRenderer())
128    );
129  }
130
131  public SequenceViewerEvent processMouseEvent(
132    SequenceRenderContext src,
133    MouseEvent me,
134    List path
135  ) {
136    path.add(this);
137    List layers = layer(src);
138    SequenceViewerEvent sve = LayeredRenderer.INSTANCE.processMouseEvent(
139      layers,
140      me,
141      path,
142      Collections.nCopies(layers.size(), getRenderer())
143    );
144
145    if(sve == null) {
146      sve = new SequenceViewerEvent(
147        this,
148        null,
149        src.graphicsToSequence(me.getPoint()),
150        me,
151        path
152      );
153    }
154
155    return sve;
156  }
157
158  private CacheMap contextCache = new FixedSizeMap(5);
159  private Set flushers = new HashSet();
160
161  protected List layer(SequenceRenderContext src) {
162    FeatureFilter filt = FilterUtils.overlapsLocation(src.getRange());
163    CtxtFilt gopher = new CtxtFilt(src, filt, false);
164    List layers = (List) contextCache.get(gopher);
165    if(layers == null) {
166      layers = doLayer(src, filt);
167      contextCache.put(gopher, layers);
168      CacheFlusher cf = new CacheFlusher(gopher);
169      ((Changeable) src.getSymbols()).addChangeListener(cf, FeatureHolder.FEATURES);
170      flushers.add(cf);
171    }
172
173    return layers;
174  }
175
176  protected List doLayer(SequenceRenderContext src, FeatureFilter filt) {
177    FeatureHolder features = src.getFeatures();
178    List layers = new ArrayList();
179    List layerLocs = new ArrayList();
180    int lead = (int) (leadingPixles / src.getScale());
181    int trail = (int) (trailingPixles / src.getScale());
182
183    for(
184      Iterator fi = features.filter(
185        filt, false
186      ).features();
187      fi.hasNext();
188    ) {
189      Feature f = (Feature) fi.next();
190      try {
191        Location fLoc = f.getLocation();
192        fLoc = new RangeLocation(fLoc.getMin() - lead, fLoc.getMax() + trail);
193
194        Iterator li = layerLocs.iterator();
195        Iterator fhI = layers.iterator();
196        SimpleFeatureHolder fhLayer = null;
197        List listLayer = null;
198      LAYER:
199        while(li.hasNext()) {
200          List l = (List) li.next();
201          SimpleFeatureHolder fh = (SimpleFeatureHolder) fhI.next();
202          for(Iterator locI = l.iterator(); locI.hasNext(); ) {
203            Location loc = (Location) locI.next();
204            if(loc.overlaps(fLoc)) {
205              continue LAYER;
206            }
207          }
208          listLayer = l;
209          fhLayer = fh;
210          break;
211        }
212        if(listLayer == null) {
213          layerLocs.add(listLayer = new ArrayList());
214          layers.add(fhLayer = new SimpleFeatureHolder());
215        }
216        listLayer.add(fLoc);
217        fhLayer.addFeature(f);
218      } catch (ChangeVetoException cve) {
219        throw new BioError("Pants", cve);
220      } catch (Throwable t) {
221        throw new AssertionFailure("Could not bump feature: " + f, t);
222      }
223    }
224
225    List contexts = new ArrayList(layers.size());
226    for(Iterator i = layers.iterator(); i.hasNext(); ) {
227      FeatureHolder layer = (FeatureHolder) i.next();
228      contexts.add(new SubSequenceRenderContext(
229        src,
230        null,
231        layer,
232        null
233      ));
234    }
235
236    return contexts;
237  }
238
239  private class CacheFlusher implements ChangeListener {
240    private CtxtFilt ctxtFilt;
241
242    public CacheFlusher(CtxtFilt ctxtFilt) {
243      this.ctxtFilt = ctxtFilt;
244    }
245
246    public void preChange(ChangeEvent ce) {
247    }
248
249    public void postChange(ChangeEvent ce) {
250      contextCache.remove(ctxtFilt);
251      flushers.remove(this);
252
253      if(hasListeners()) {
254        ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
255        synchronized(cs) {
256          ChangeEvent ce2 = new ChangeEvent(
257            BumpedRenderer.this,
258            SequenceRenderContext.LAYOUT
259          );
260          cs.firePostChangeEvent(ce2);
261        }
262      }
263    }
264  }
265
266  private class CtxtFilt {
267    private SequenceRenderContext src;
268    private FeatureFilter filter;
269    private boolean recurse;
270
271    public CtxtFilt(SequenceRenderContext src, FeatureFilter filter, boolean recurse) {
272      this.src = src;
273      this.filter = filter;
274      this.recurse = recurse;
275    }
276
277    public boolean equals(Object o) {
278      if(! (o instanceof CtxtFilt) ) {
279        return false;
280      }
281      CtxtFilt that = (CtxtFilt) o;
282      return
283        src.equals(that.src) &&
284        filter.equals(that.filter) &&
285        (recurse == that.recurse);
286    }
287
288    public int hashCode() {
289      return src.hashCode() ^ filter.hashCode();
290    }
291  }
292}