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.BasicStroke;
025import java.awt.Color;
026import java.awt.Graphics2D;
027import java.awt.Paint;
028import java.awt.Stroke;
029import java.awt.event.MouseEvent;
030import java.io.Serializable;
031import java.util.Collection;
032import java.util.HashMap;
033import java.util.Iterator;
034import java.util.Map;
035import java.util.Set;
036
037import org.biojava.bio.seq.Feature;
038import org.biojava.bio.seq.FeatureFilter;
039import org.biojava.bio.seq.FeatureHolder;
040import org.biojava.bio.seq.OptimizableFilter;
041import org.biojava.utils.AbstractChangeable;
042import org.biojava.utils.ChangeEvent;
043import org.biojava.utils.ChangeSupport;
044import org.biojava.utils.ChangeType;
045import org.biojava.utils.ChangeVetoException;
046
047/**
048 * <p><code>AbstractBeadRenderer</code> is a an abstract base class
049 * for the creation of <code>FeatureRenderer</code>s which use a
050 * 'string of beads' metaphor for displaying features. Each subclass
051 * of <code>AbstractBeadRenderer</code> should override the abstract
052 * method <code>renderBead()</code> and provide the drawing routine
053 * for its particular bead type.</p>
054 *
055 * <p>A concrete <code>BeadFeatureRenderer</code> may render a series
056 * of features in more than one style by delegating to other
057 * <code>BeadFeatureRenderer</code>s for the additional style(s). This
058 * is achieved using the <code>setDelegateRenderer()</code> method
059 * which associates an <code>OptimizableFilter</code> with another
060 * <code>BeadFeatureRenderer</code>. Any feature accepted by the
061 * filter is rendered with that renderer, while the remainder are
062 * rendered by the current renderer.</p>
063 *
064 * @author Keith James
065 * @author Paul Seed
066 * @since 1.2
067 */
068public abstract class AbstractBeadRenderer extends AbstractChangeable
069    implements BeadFeatureRenderer, Serializable
070{
071    /**
072     * Constant <code>DISPLACEMENT</code> indicating a change to the
073     * Y-axis displacement of the features.
074     */
075    public static final ChangeType DISPLACEMENT =
076        new ChangeType("The displacement of the features has changed",
077                       "org.biojava.bio.gui.sequence.AbstractBeadRenderer",
078                       "DISPLACEMENT", SequenceRenderContext.LAYOUT);
079    
080    /**
081     * Constant <code>DEPTH</code> indicating a change to the depth of
082     * the renderer.
083     */
084    public static final ChangeType DEPTH =
085        new ChangeType("The depth of the renderer has changed",
086                       "org.biojava.bio.gui.sequence.AbstractBeadRenderer",
087                       "DEPTH", SequenceRenderContext.LAYOUT);
088
089    /**
090     * Constant <code>OUTLINE</code> indicating a change to the
091     * outline paint of the features.
092     */
093    public static final ChangeType OUTLINE =
094        new ChangeType("The outline of the features has changed",
095                       "org.biojava.bio.gui.sequence.AbstractBeadRenderer",
096                       "OUTLINE", SequenceRenderContext.REPAINT);
097
098    /**
099     * Constant <code>STROKE</code> indicating a change to the outline
100     * stroke of the features.
101     */
102    public static final ChangeType STROKE =
103        new ChangeType("The stroke of the features has changed",
104                       "org.biojava.bio.gui.sequence.AbstractBeadRenderer",
105                       "STROKE", SequenceRenderContext.REPAINT);
106
107    /**
108     * Constant <code>FILL</code> indicating a change to the fill of
109     * the features.
110     */
111    public static final ChangeType FILL =
112        new ChangeType("The fill of the features has changed",
113                       "org.biojava.bio.gui.sequence.AbstractBeadRenderer",
114                       "FILL", SequenceRenderContext.REPAINT);
115
116    protected double        beadDepth;
117    protected double beadDisplacement;
118    protected Paint       beadOutline;
119    protected Paint          beadFill;
120    protected Stroke       beadStroke;
121
122    // Map of FeatureFilter -> FeatureRenderer
123    protected Map delegates;
124    // Map of Feature -> FeatureRenderer
125    protected Map delegationCache;
126
127    /**
128     * Creates a new <code>AbstractBeadRenderer</code> with no
129     * delegates. It will render all features itself, using its own
130     * style settings.
131     */
132    public AbstractBeadRenderer()
133    {
134        this(10.0f, 0.0f, Color.black, Color.black, new BasicStroke());
135    }
136
137    /**
138     * Creates a new <code>AbstractBeadRenderer</code> object.
139     *
140     * @param beadDepth a <code>double</code>.
141     * @param beadDisplacement a <code>double</code>.
142     * @param beadOutline a <code>Paint</code>.
143     * @param beadFill a <code>Paint</code>.
144     * @param beadStroke a <code>Stroke</code>.
145     */
146    public AbstractBeadRenderer(double beadDepth,
147                                double beadDisplacement,
148                                Paint  beadOutline,
149                                Paint  beadFill,
150                                Stroke beadStroke)
151    {
152        this.beadDepth        = beadDepth;
153        this.beadDisplacement = beadDisplacement;
154        this.beadOutline      = beadOutline;
155        this.beadFill         = beadFill;
156        this.beadStroke       = beadStroke;
157
158        delegates       = new HashMap();
159        delegationCache = new HashMap();
160    }
161
162    /**
163     * <code>processMouseEvent</code> defines the behaviour on
164     * revieving a mouse event.
165     *
166     * @param holder a <code>FeatureHolder</code>.
167     * @param context a <code>SequenceRenderContext</code>.
168     * @param mEvent a <code>MouseEvent</code>.
169     *
170     * @return a <code>FeatureHolder</code>.
171     */
172    public FeatureHolder processMouseEvent(FeatureHolder         holder,
173                                           SequenceRenderContext context,
174                                           MouseEvent            mEvent)
175    {
176        return holder;
177    }
178
179    /**
180     * <code>renderFeature</code> draws a feature using the supplied
181     * graphics context. The rendering may be delegated to another
182     * <code>FeatureRenderer</code> instance.
183     *
184     * @param g2 a <code>Graphics2D</code> context.
185     * @param f a <code>Feature</code> to render.
186     * @param context a <code>SequenceRenderContext</code> context.
187     */
188    public void renderFeature(Graphics2D            g2,
189                              Feature               f,
190                              SequenceRenderContext context)
191    {
192        // Check the cache first
193        if (delegationCache.containsKey(f))
194        {
195            // System.err.println("Used cache for: " + f);
196
197            BeadFeatureRenderer cachedRenderer =
198                (BeadFeatureRenderer) delegationCache.get(f);
199
200            cachedRenderer.renderBead(g2, f, context);
201            return;
202        }
203
204        for (Iterator di = delegates.keySet().iterator(); di.hasNext();)
205        {
206            FeatureFilter filter = (FeatureFilter) di.next();
207
208            if (filter.accept(f))
209            {
210                // System.err.println(filter + " accepted " + f);
211
212                FeatureRenderer delegate =
213                    (FeatureRenderer) delegates.get(filter);
214
215                delegate.renderFeature(g2, f, context);
216                return;
217            }
218        }
219
220        delegationCache.put(f, this);
221        // System.err.println("Rendering: " + f);
222        renderBead(g2, f, context);
223    }
224
225    /**
226     * <code>setDelegateRenderer</code> associates an
227     * <code>OptimizableFilter</code> with a
228     * <code>BeadFeatureRenderer</code>. Any feature accepted by the
229     * filter will be passed to the associated renderer for
230     * drawing. The <code>OptimizableFilter</code>s should be disjoint
231     * with respect to each other (a feature may not be rendered more
232     * than once).
233     *
234     * @param filter an <code>OptimizableFilter</code>.
235     * @param renderer a <code>BeadFeatureRenderer</code>.
236     *
237     * @exception IllegalArgumentException if the filter is not
238     * disjoint with existing delegate filters.
239     */
240    public void setDelegateRenderer(OptimizableFilter   filter,
241                                    BeadFeatureRenderer renderer)
242        throws IllegalArgumentException
243    {
244        // Ensure the cache doesn't hide the new delegate
245        delegationCache.clear();
246        
247        Set delegateFilters = delegates.keySet();
248
249        if (delegateFilters.size() == 0)
250        {
251            delegates.put(filter, renderer);
252        }
253        else
254        {
255            for (Iterator fi = delegateFilters.iterator(); fi.hasNext();)
256            {
257                OptimizableFilter thisFilter = (OptimizableFilter) fi.next();
258
259                if (! thisFilter.isDisjoint(filter))
260                {
261                    throw new IllegalArgumentException("Failed to apply filter as it clashes with existing filter "
262                                                       + thisFilter
263                                                       + " (filters must be disjoint)");
264                }
265                else
266                {
267                    delegates.put(filter, renderer);
268                    break;
269                }
270            }
271        }
272    }
273
274    /**
275     * <code>removeDelegateRenderer</code> removes any association
276     * of the given <code>OptimizableFilter</code> with a
277     * <code>BeadFeatureRenderer</code>.
278     *
279     * @param filter an <code>OptimizableFilter</code>.
280     */
281    public void removeDelegateRenderer(OptimizableFilter   filter)
282    {
283        // Ensure the cache doesn't hide the change of delegate
284        delegationCache.clear();
285        delegates.remove(filter);
286    }
287
288    /**
289     * <code>getDepth</code> calculates the depth required by this
290     * renderer to display its beads. It recurses through its delegate
291     * renderers and returns the highest value. Concrete renderers
292     * should override this method and supply code to calculate their
293     * own depth. If a subclass needs to know the depth of its
294     * delegates (as is likely if it has any) they can call this
295     * method using <code>super.getDepth()</code>.
296     *
297     * @param context a <code>SequenceRenderContext</code>.
298     *
299     * @return a <code>double</code>.
300     */
301    public double getDepth(SequenceRenderContext context)
302    {
303        Collection delegateRenderers = delegates.values();
304        double maxDepth = 0.0;
305
306        if (delegateRenderers.size() == 0)
307        {
308            return maxDepth + 1.0;
309        }
310        else
311        {
312            for (Iterator ri = delegateRenderers.iterator(); ri.hasNext();)
313            {
314                maxDepth = Math.max(maxDepth, ((FeatureRenderer) ri.next()).getDepth(context));
315            }
316
317            return maxDepth + 1.0;
318        }
319    }
320
321    /**
322     * <code>getBeadDepth</code> returns the depth of a single bead
323     * produced by this renderer.
324     *
325     * @return a <code>double</code>.
326     */
327    public double getBeadDepth()
328    {
329        return beadDepth;
330    }
331
332    /**
333     * <code>setBeadDepth</code> sets the depth of a single bead
334     * produced by this renderer.
335     *
336     * @param depth a <code>double</code>.
337     *
338     * @exception ChangeVetoException if an error occurs.
339     */
340    public void setBeadDepth(double depth) throws ChangeVetoException
341    {
342        if (hasListeners())
343        {
344            ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
345            synchronized(cs)
346            {
347                ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT,
348                                                 null, null,
349                                                 new ChangeEvent(this, DEPTH,
350                                                                 new Double(beadDepth),
351                                                                 new Double(depth)));
352                cs.firePreChangeEvent(ce);
353                beadDepth = depth;
354                cs.firePostChangeEvent(ce);
355            }
356        }
357        else
358        {
359            beadDepth = depth;
360        }
361    }
362
363    /**
364     * <code>getBeadDisplacement</code> returns the displacement of
365     * beads from the centre line of the renderer. A positive value
366     * indicates displacment downwards (for horizontal renderers) or
367     * to the right (for vertical renderers).
368     *
369     * @return a <code>double</code>.
370     */
371    public double getBeadDisplacement()
372    {
373        return beadDisplacement;
374    }
375
376    /**
377     * <code>setBeadDisplacement</code> sets the displacement of
378     * beads from the centre line of the renderer. A positive value
379     * indicates displacment downwards (for horizontal renderers) or
380     * to the right (for vertical renderers).
381     *
382     * @param displacement a <code>double</code>.
383     *
384     * @exception ChangeVetoException if an error occurs.
385     */
386    public void setBeadDisplacement(double displacement)
387        throws ChangeVetoException
388    {
389        if (hasListeners())
390        {
391            ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
392            synchronized(cs)
393            {
394                ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT,
395                                                 null, null,
396                                                 new ChangeEvent(this, DISPLACEMENT,
397                                                                 new Double(beadDisplacement),
398                                                                 new Double(displacement)));
399                cs.firePreChangeEvent(ce);
400                beadDisplacement = displacement;
401                cs.firePostChangeEvent(ce);
402            }
403        }
404        else
405        {
406            beadDisplacement = displacement;
407        }
408    }
409
410    /**
411     * <code>getBeadOutline</code> returns the bead outline paint.
412     *
413     * @return a <code>Paint</code>.
414     */
415    public Paint getBeadOutline()
416    {
417        return beadOutline;
418    }
419
420    /**
421     * <code>setBeadOutline</code> sets the bead outline paint.
422     *
423     * @param outline a <code>Paint</code>.
424     *
425     * @exception ChangeVetoException if an error occurs.
426     */
427    public void setBeadOutline(Paint outline) throws ChangeVetoException
428    {
429        if (hasListeners())
430        {
431            ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
432            synchronized(cs)
433            {
434                ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT,
435                                                 null, null,
436                                                 new ChangeEvent(this, OUTLINE,
437                                                                 outline,
438                                                                 beadOutline));
439                cs.firePreChangeEvent(ce);
440                beadOutline = outline;
441                cs.firePostChangeEvent(ce);
442            }
443        }
444        else
445        {
446            beadOutline = outline;
447        }
448    }
449
450    /**
451     * <code>getBeadStroke</code> returns the bead outline stroke.
452     *
453     * @return a <code>Stroke</code>.
454     */
455    public Stroke getBeadStroke()
456    {
457        return beadStroke;
458    }
459
460    /**
461     * <code>setBeadStroke</code> sets the bead outline stroke.
462     *
463     * @param stroke a <code>Stroke</code>.
464     *
465     * @exception ChangeVetoException if an error occurs.
466     */
467    public void setBeadStroke(Stroke stroke) throws ChangeVetoException
468    {
469        if (hasListeners())
470        {
471            ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
472            synchronized(cs)
473            {
474                ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT,
475                                                 null, null,
476                                                 new ChangeEvent(this, STROKE,
477                                                                 stroke,
478                                                                 beadStroke));
479                cs.firePreChangeEvent(ce);
480                beadStroke = stroke;
481                cs.firePostChangeEvent(ce);
482            }
483        }
484        else
485        {
486            beadStroke = stroke;
487        }
488    } 
489
490    /**
491     * <code>getBeadFill</code> returns the bead fill paint.
492     *
493     * @return a <code>Paint</code>.
494     */
495    public Paint getBeadFill()
496    {
497        return beadFill;
498    }
499
500    /**
501     * <code>setBeadFill</code> sets the bead fill paint.
502     *
503     * @param fill a <code>Paint</code>.
504     *
505     * @exception ChangeVetoException if an error occurs.
506     */
507    public void setBeadFill(Paint fill) throws ChangeVetoException
508    {
509        if (hasListeners())
510        {
511            ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
512            synchronized(cs)
513            {
514                ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT,
515                                                 null, null,
516                                                 new ChangeEvent(this, FILL,
517                                                                 fill,
518                                                                 beadFill));
519                cs.firePreChangeEvent(ce);
520                beadFill = fill;
521                cs.firePostChangeEvent(ce);
522            }
523        }
524        else
525        {
526            beadFill = fill;
527        }
528    }
529
530    /**
531     * <code>renderBead</code> should be overridden by the concrete
532     * <code>BeadRenderer</code>.
533     *
534     * @param g2 a <code>Graphics2D</code>.
535     * @param f a <code>Feature</code> to render.
536     * @param context a <code>SequenceRenderContext</code> context.
537     */
538    public abstract void renderBead(Graphics2D            g2,
539                                    Feature               f,
540                                    SequenceRenderContext context);
541}