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}