/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojava.bio.gui.sequence; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.util.Iterator; import java.util.List; import org.biojava.bio.seq.Feature; import org.biojava.bio.seq.FeatureFilter; import org.biojava.bio.seq.FeatureHolder; import org.biojava.bio.seq.FilterUtils; import org.biojava.bio.symbol.RangeLocation; import org.biojava.utils.AbstractChangeable; import org.biojava.utils.AssertionFailure; import org.biojava.utils.ChangeEvent; import org.biojava.utils.ChangeForwarder; import org.biojava.utils.ChangeSupport; import org.biojava.utils.ChangeType; import org.biojava.utils.ChangeVetoException; import org.biojava.utils.Changeable; /** * FeatureBlockSequenceRenderer forms a bridge between * Sequence rendering and Feature * rendering. It is a SequenceRenderer which iterates * through a Sequence's Features and makes * method calls on a FeatureRenderer. * * @author Matthew Pocock * @author Keith James * @author Thomas Down */ public class FeatureBlockSequenceRenderer extends AbstractChangeable implements SequenceRenderer { public static ChangeType FEATURE_RENDERER = new ChangeType("The associated FeatureRenderer has changed", "org.biojava.bio.gui.sequence.FeatureBlockSequenceRenderer", "FEATURE_RENDERER", SequenceRenderContext.LAYOUT); public static ChangeType FEATURE_COLLAPSING = new ChangeType("Changed whether the render collapses when no features are visible", "org.biojava.bio.gui.sequence.FeatureBlockSequenceRenderer", "FEATURE_COLLAPSING", SequenceRenderContext.LAYOUT); private FeatureRenderer renderer; private boolean isCollapsing = true;; private transient ChangeForwarder rendForwarder; protected ChangeSupport getChangeSupport(ChangeType ct) { ChangeSupport cs = super.getChangeSupport(ct); if (rendForwarder == null) { rendForwarder = new SequenceRenderer.RendererForwarder(this, cs); if ((renderer != null) && (renderer instanceof Changeable)) { Changeable c = (Changeable) this.renderer; c.addChangeListener(rendForwarder, SequenceRenderContext.REPAINT); } } return cs; } /** * Creates a new FeatureBlockSequenceRenderer which * uses a BasicFeatureRenderer as its renderer. */ public FeatureBlockSequenceRenderer() { try { setFeatureRenderer(new BasicFeatureRenderer()); } catch (ChangeVetoException cve) { throw new AssertionFailure("Assertion Failure: Should have no listeners", cve); } } /** * Creates a new FeatureBlockSequenceRenderer which * uses the specified FeatureRenderer. * * @param fRend a FeatureRenderer. */ public FeatureBlockSequenceRenderer(FeatureRenderer fRend) { try { setFeatureRenderer(fRend); } catch (ChangeVetoException cve) { throw new AssertionFailure("Assertion Failure: Should have no listeners", cve); } } /** * getFeatureRenderer returns the currently active * renderer. * * @return a FeatureRenderer. */ public FeatureRenderer getFeatureRenderer() { return renderer; } /** * setFeatureRenderer sets the renderer to be used. * * @param renderer a FeatureRenderer. * @exception ChangeVetoException if the renderer can not be * changed. */ public void setFeatureRenderer(FeatureRenderer renderer) throws ChangeVetoException { if (hasListeners()) { ChangeSupport cs = getChangeSupport(FEATURE_RENDERER); synchronized(cs) { ChangeEvent ce = new ChangeEvent(this, FEATURE_RENDERER, this.renderer, renderer); cs.firePreChangeEvent(ce); if ((this.renderer != null) && (this.renderer instanceof Changeable)) { Changeable c = (Changeable) this.renderer; c.removeChangeListener(rendForwarder, SequenceRenderContext.REPAINT); } this.renderer = renderer; if (renderer instanceof Changeable) { Changeable c = (Changeable) renderer; c.removeChangeListener(rendForwarder, SequenceRenderContext.REPAINT); } cs.firePostChangeEvent(ce); } } else { this.renderer = renderer; } } /** * Specifies if the renderer should collapse to zero depth when no * features are visible (default true). * * @since 1.3 */ public void setCollapsing(boolean b) throws ChangeVetoException { if (hasListeners()) { ChangeSupport cs = getChangeSupport(FEATURE_COLLAPSING); synchronized (cs) { ChangeEvent ce = new ChangeEvent(this, FEATURE_COLLAPSING, new Boolean(this.isCollapsing), new Boolean(b)); cs.firePreChangeEvent(ce); this.isCollapsing = b; cs.firePostChangeEvent(ce); } } else { this.isCollapsing = b; } } /** * Returns true if this class collapses to zero depth when there are * no visible features. * * @since 1.3 */ public boolean getCollapsing() { return isCollapsing; } public double getDepth(SequenceRenderContext src) { FeatureHolder features = src.getFeatures(); FeatureFilter filter = FilterUtils.overlapsLocation(src.getRange()); FeatureHolder fh = features.filter(filter, false); if (!isCollapsing || fh.countFeatures() > 0) { return renderer.getDepth(src); } else { return 0.0; } } public double getMinimumLeader(SequenceRenderContext src) { return 0.0; } public double getMinimumTrailer(SequenceRenderContext src) { return 0.0; } public void paint(Graphics2D g, SequenceRenderContext src) { FeatureFilter filt = FilterUtils.shadowOverlapsLocation( GUITools.getVisibleRange(src, g) ); FeatureHolder feats = src.getFeatures().filter(filt, false); for (Iterator i =feats.features(); i.hasNext();) { Shape clip = g.getClip(); AffineTransform at = g.getTransform(); Feature f = (Feature) i.next(); renderer.renderFeature(g, f, src); g.setTransform(at); g.setClip(clip); } } public SequenceViewerEvent processMouseEvent(SequenceRenderContext src, MouseEvent me, List path) { double pos; if (src.getDirection() == SequenceRenderContext.HORIZONTAL) { pos = me.getPoint().getX(); } else { pos = me.getPoint().getY(); } int sMin = src.graphicsToSequence(pos); int sMax = src.graphicsToSequence(pos + 1); FeatureHolder hits = src.getFeatures().filter(new FeatureFilter.OverlapsLocation(new RangeLocation(sMin, sMax)), false); hits = renderer.processMouseEvent(hits, src, me); return new SequenceViewerEvent(this, hits, sMin, me, path); } }