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.Shape; 026import java.awt.event.MouseEvent; 027import java.awt.geom.AffineTransform; 028import java.util.Iterator; 029import java.util.List; 030 031import org.biojava.bio.seq.Feature; 032import org.biojava.bio.seq.FeatureFilter; 033import org.biojava.bio.seq.FeatureHolder; 034import org.biojava.bio.seq.FilterUtils; 035import org.biojava.bio.symbol.RangeLocation; 036import org.biojava.utils.AbstractChangeable; 037import org.biojava.utils.AssertionFailure; 038import org.biojava.utils.ChangeEvent; 039import org.biojava.utils.ChangeForwarder; 040import org.biojava.utils.ChangeSupport; 041import org.biojava.utils.ChangeType; 042import org.biojava.utils.ChangeVetoException; 043import org.biojava.utils.Changeable; 044 045/** 046 * <code>FeatureBlockSequenceRenderer</code> forms a bridge between 047 * <code>Sequence</code> rendering and <code>Feature</code> 048 * rendering. It is a <code>SequenceRenderer</code> which iterates 049 * through a <code>Sequence</code>'s <code>Feature</code>s and makes 050 * method calls on a <code>FeatureRenderer</code>. 051 * 052 * @author Matthew Pocock 053 * @author Keith James 054 * @author Thomas Down 055 */ 056public class FeatureBlockSequenceRenderer extends AbstractChangeable 057 implements SequenceRenderer { 058 public static ChangeType FEATURE_RENDERER = 059 new ChangeType("The associated FeatureRenderer has changed", 060 "org.biojava.bio.gui.sequence.FeatureBlockSequenceRenderer", 061 "FEATURE_RENDERER", 062 SequenceRenderContext.LAYOUT); 063 public static ChangeType FEATURE_COLLAPSING = 064 new ChangeType("Changed whether the render collapses when no features are visible", 065 "org.biojava.bio.gui.sequence.FeatureBlockSequenceRenderer", 066 "FEATURE_COLLAPSING", 067 SequenceRenderContext.LAYOUT); 068 069 private FeatureRenderer renderer; 070 private boolean isCollapsing = true;; 071 private transient ChangeForwarder rendForwarder; 072 073 protected ChangeSupport getChangeSupport(ChangeType ct) { 074 ChangeSupport cs = super.getChangeSupport(ct); 075 076 if (rendForwarder == null) { 077 rendForwarder = new SequenceRenderer.RendererForwarder(this, cs); 078 if ((renderer != null) && (renderer instanceof Changeable)) { 079 Changeable c = (Changeable) this.renderer; 080 c.addChangeListener(rendForwarder, 081 SequenceRenderContext.REPAINT); 082 } 083 } 084 085 return cs; 086 } 087 088 /** 089 * Creates a new <code>FeatureBlockSequenceRenderer</code> which 090 * uses a <code>BasicFeatureRenderer</code> as its renderer. 091 */ 092 public FeatureBlockSequenceRenderer() { 093 try { 094 setFeatureRenderer(new BasicFeatureRenderer()); 095 } catch (ChangeVetoException cve) { 096 throw new AssertionFailure("Assertion Failure: Should have no listeners", cve); 097 } 098 } 099 100 /** 101 * Creates a new <code>FeatureBlockSequenceRenderer</code> which 102 * uses the specified <code>FeatureRenderer</code>. 103 * 104 * @param fRend a <code>FeatureRenderer</code>. 105 */ 106 public FeatureBlockSequenceRenderer(FeatureRenderer fRend) { 107 try { 108 setFeatureRenderer(fRend); 109 } catch (ChangeVetoException cve) { 110 throw new AssertionFailure("Assertion Failure: Should have no listeners", cve); 111 } 112 } 113 114 /** 115 * <code>getFeatureRenderer</code> returns the currently active 116 * renderer. 117 * 118 * @return a <code>FeatureRenderer</code>. 119 */ 120 public FeatureRenderer getFeatureRenderer() { 121 return renderer; 122 } 123 124 /** 125 * <code>setFeatureRenderer</code> sets the renderer to be used. 126 * 127 * @param renderer a <code>FeatureRenderer</code>. 128 * @exception ChangeVetoException if the renderer can not be 129 * changed. 130 */ 131 public void setFeatureRenderer(FeatureRenderer renderer) 132 throws ChangeVetoException { 133 if (hasListeners()) { 134 ChangeSupport cs = getChangeSupport(FEATURE_RENDERER); 135 synchronized(cs) { 136 ChangeEvent ce = new ChangeEvent(this, 137 FEATURE_RENDERER, 138 this.renderer, 139 renderer); 140 cs.firePreChangeEvent(ce); 141 if ((this.renderer != null) && 142 (this.renderer instanceof Changeable)) { 143 Changeable c = (Changeable) this.renderer; 144 c.removeChangeListener(rendForwarder, SequenceRenderContext.REPAINT); 145 } 146 this.renderer = renderer; 147 if (renderer instanceof Changeable) { 148 Changeable c = (Changeable) renderer; 149 c.removeChangeListener(rendForwarder, SequenceRenderContext.REPAINT); 150 } 151 cs.firePostChangeEvent(ce); 152 } 153 } else { 154 this.renderer = renderer; 155 } 156 } 157 158 /** 159 * Specifies if the renderer should collapse to zero depth when no 160 * features are visible (default <code>true</code>). 161 * 162 * @since 1.3 163 */ 164 165 public void setCollapsing(boolean b) 166 throws ChangeVetoException 167 { 168 if (hasListeners()) { 169 ChangeSupport cs = getChangeSupport(FEATURE_COLLAPSING); 170 synchronized (cs) { 171 ChangeEvent ce = new ChangeEvent(this, 172 FEATURE_COLLAPSING, 173 new Boolean(this.isCollapsing), 174 new Boolean(b)); 175 cs.firePreChangeEvent(ce); 176 this.isCollapsing = b; 177 cs.firePostChangeEvent(ce); 178 } 179 } else { 180 this.isCollapsing = b; 181 } 182 } 183 184 /** 185 * Returns <code>true</code> if this class collapses to zero depth when there are 186 * no visible features. 187 * 188 * @since 1.3 189 */ 190 191 public boolean getCollapsing() { 192 return isCollapsing; 193 } 194 195 public double getDepth(SequenceRenderContext src) { 196 FeatureHolder features = src.getFeatures(); 197 FeatureFilter filter = 198 FilterUtils.overlapsLocation(src.getRange()); 199 FeatureHolder fh = features.filter(filter, false); 200 if (!isCollapsing || fh.countFeatures() > 0) { 201 return renderer.getDepth(src); 202 } else { 203 return 0.0; 204 } 205 } 206 207 public double getMinimumLeader(SequenceRenderContext src) { 208 return 0.0; 209 } 210 211 public double getMinimumTrailer(SequenceRenderContext src) { 212 return 0.0; 213 } 214 215 public void paint(Graphics2D g, SequenceRenderContext src) { 216 FeatureFilter filt = FilterUtils.shadowOverlapsLocation( 217 GUITools.getVisibleRange(src, g) ); 218 FeatureHolder feats = src.getFeatures().filter(filt, false); 219 220 for (Iterator i =feats.features(); i.hasNext();) { 221 Shape clip = g.getClip(); 222 AffineTransform at = g.getTransform(); 223 224 Feature f = (Feature) i.next(); 225 renderer.renderFeature(g, f, src); 226 227 g.setTransform(at); 228 g.setClip(clip); 229 } 230 } 231 232 public SequenceViewerEvent processMouseEvent(SequenceRenderContext src, 233 MouseEvent me, 234 List path) { 235 double pos; 236 if (src.getDirection() == SequenceRenderContext.HORIZONTAL) { 237 pos = me.getPoint().getX(); 238 } else { 239 pos = me.getPoint().getY(); 240 } 241 242 int sMin = src.graphicsToSequence(pos); 243 int sMax = src.graphicsToSequence(pos + 1); 244 245 FeatureHolder hits = 246 src.getFeatures().filter(new FeatureFilter.OverlapsLocation(new RangeLocation(sMin, sMax)), 247 false); 248 249 hits = renderer.processMouseEvent(hits, src, me); 250 251 return new SequenceViewerEvent(this, hits, sMin, me, path); 252 } 253} 254