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