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.Dimension;
025import java.awt.Graphics;
026import java.awt.Graphics2D;
027import java.awt.Insets;
028import java.awt.Point;
029import java.awt.RenderingHints;
030import java.awt.Shape;
031import java.awt.event.MouseAdapter;
032import java.awt.event.MouseEvent;
033import java.awt.event.MouseListener;
034import java.awt.event.MouseMotionListener;
035import java.awt.geom.AffineTransform;
036import java.awt.geom.Point2D;
037import java.awt.geom.Rectangle2D;
038import java.beans.PropertyChangeEvent;
039import java.beans.PropertyChangeListener;
040import java.io.Serializable;
041import java.util.ArrayList;
042
043import javax.swing.JComponent;
044
045import org.biojava.bio.seq.FeatureHolder;
046import org.biojava.bio.seq.Sequence;
047import org.biojava.bio.symbol.RangeLocation;
048import org.biojava.bio.symbol.SymbolList;
049import org.biojava.utils.ChangeAdapter;
050import org.biojava.utils.ChangeEvent;
051import org.biojava.utils.ChangeListener;
052import org.biojava.utils.ChangeSupport;
053import org.biojava.utils.ChangeType;
054import org.biojava.utils.ChangeVetoException;
055import org.biojava.utils.Changeable;
056
057/**
058 * <p>A <code>PairwiseSequencePanel</code> is a panel that displays a
059 * pair of sequences; one sequence (the primary) may be either
060 * left-to-right (HORIZONTAL) or from top-to-bottom (VERTICAL). The
061 * other sequence (secondary) will then occupy the remaining
062 * direction. It has an associated scale which is the number of pixels
063 * per symbol and applies to both sequences. The leading and trailing
064 * borders apply to the primary sequence only.</p>
065 *
066 * <p>The primary purpose of this component is to provide a means for
067 * representing graphically comparisons between two sequences. This
068 * could be anything from traditional dotplots (or variants created
069 * with lines) to a more complex layered plot involving superimposed
070 * renderers.</p>
071 *
072 * <p>Each sequence has a translation which is the number of
073 * <code>Symbol</code>s to skip before rendering starts. In order to
074 * produce a scrolling effect, the <code>setSymbolTranslation</code>
075 * or <code>setSecondarySymbolTranslation</code> method may be hooked
076 * up to an <code>Adjustable</code> such as <code>JScrollBar</code> or
077 * to an event listener.</p>
078 *
079 * <p>The exact number of <code>Symbol</code>s rendered in each
080 * sequence depends on the dimensions of the panel and the
081 * scale. Resizing the panel will cause the number of
082 * <code>Symbol</code>s rendered to change accordingly.</p>
083 *
084 * <p>The panel will fill its background to the <code>Color</code>
085 * defined by the <code>setBackground()</code> method provided that it
086 * has been defined as opaque using <code>setOpaque()</code>.</p>
087 *
088 * <p>The change event handling code is based on the original panel
089 * and other BioJava components by Matthew and Thomas.</p>
090 *
091 * @author Keith James
092 * @author Matthew Pocock
093 * @since 1.2
094 */
095public class PairwiseSequencePanel extends JComponent
096    implements PairwiseRenderContext, Changeable, Serializable
097{
098    /**
099     * Constant <code>RENDERER</code> is a <code>ChangeType</code>
100     * which indicates a change to the renderer, requiring a layout
101     * update.
102     */
103    public static final ChangeType RENDERER =
104        new ChangeType("The renderer for this PairwiseSequencePanel has changed",
105                       "org.biojava.bio.gui.sequence.PairwiseSequencePanel",
106                       "RENDERER", SequenceRenderContext.LAYOUT);
107
108    /**
109     * Constant <code>TRANSLATION</code> is a <code>ChangeType</code>
110     * which indicates a change to the translation, requiring a paint
111     * update.
112     */
113    public static final ChangeType TRANSLATION =
114        new ChangeType("The translation for this PairwiseSequencePanel has changed",
115                       "org.biojava.bio.gui.sequence.PairwiseSequencePanel",
116                       "TRANSLATION", SequenceRenderContext.REPAINT);
117
118    // The query sequence to be rendered
119    private Sequence sequence;
120    // The number of residues to skip before starting to render
121    private int translation;
122    // The rendering direction (HORIZONTAL or VERTICAL)
123    private int direction;
124
125    // The subject sequence to be rendered
126    private Sequence secSequence;
127    // The number of residues to skip before starting to render
128    private int secTranslation;
129    // The rendering direction (HORIZONTAL or VERTICAL)
130    private int secDirection;
131
132    // The rendering scale in pixels per residue
133    private double scale;
134    // The homology renderer
135    private PairwiseSequenceRenderer renderer;
136
137    // RenderingHints to be used by renderers
138    private RenderingHints hints;
139
140    // The rendering context borders
141    private SequenceRenderContext.Border leadingBorder;
142    private SequenceRenderContext.Border trailingBorder;
143
144    // Listens for bound property changes which require a repaint
145    // afterwards
146    private PropertyChangeListener propertyListener =
147    new PropertyChangeListener()
148    {
149        public void propertyChange(PropertyChangeEvent pce)
150        {
151            repaint();
152        }
153    };
154
155    // ChangeSupport helper for BioJava ChangeListeners
156    private transient ChangeSupport changeSupport = null;
157
158    // Listens for BioJava changes which will require a repaint
159    // afterwards
160    private ChangeListener repaintListener = new ChangeAdapter()
161    {
162        public void postChange(ChangeEvent ce)
163        {
164            repaint();
165        }
166    };
167
168    // Listens for BioJava changes which will require a revalidation
169    // afterwards
170    private ChangeListener layoutListener = new ChangeAdapter()
171    {
172        public void postChange(ChangeEvent ce)
173        {
174            revalidate();
175        }
176    };
177
178    // SequenceViewerSupport helper for BioJava SequenceViewerListeners
179    private SequenceViewerSupport svSupport = new SequenceViewerSupport();
180
181    // Listens for mouse click events
182    private MouseListener mouseListener = new MouseAdapter()
183    {
184        public void mouseClicked(MouseEvent me)
185        {
186            if (! isActive())
187                return;
188
189            Insets insets = getInsets();
190            me.translatePoint(-insets.left, -insets.top);
191
192            SequenceViewerEvent sve =
193                renderer.processMouseEvent(PairwiseSequencePanel.this, me,
194                                           new ArrayList());
195
196            me.translatePoint(insets.left, insets.top);
197            svSupport.fireMouseClicked(sve);
198        }
199
200        public void mousePressed(MouseEvent me)
201        {
202            if (! isActive())
203                return;
204
205            Insets insets = getInsets();
206            me.translatePoint(-insets.left, -insets.top);
207
208            SequenceViewerEvent sve =
209                renderer.processMouseEvent(PairwiseSequencePanel.this, me,
210                                           new ArrayList());
211
212            me.translatePoint(insets.left, insets.top);
213            svSupport.fireMousePressed(sve);
214        }
215
216        public void mouseReleased(MouseEvent me)
217        {
218            if (! isActive())
219                return;
220
221            Insets insets = getInsets();
222            me.translatePoint(-insets.left, -insets.top);
223
224            SequenceViewerEvent sve =
225                renderer.processMouseEvent(PairwiseSequencePanel.this, me,
226                                           new ArrayList());
227
228            me.translatePoint(insets.left, insets.top);
229            svSupport.fireMouseReleased(sve);
230        }
231    };
232
233    // SequenceViewerMotionSupport helper for BioJava
234    // SequenceViewerMotionListeners
235    private SequenceViewerMotionSupport svmSupport =
236        new SequenceViewerMotionSupport();
237
238    // Listens for mouse movement events
239    private MouseMotionListener mouseMotionListener = new MouseMotionListener()
240    {
241        public void mouseDragged(MouseEvent me)
242        {
243            if (! isActive())
244                return;
245
246            Insets insets = getInsets();
247            me.translatePoint(-insets.left, -insets.top);
248
249            SequenceViewerEvent sve =
250                renderer.processMouseEvent(PairwiseSequencePanel.this, me,
251                                           new ArrayList());
252
253            me.translatePoint(insets.left, insets.top);
254            svmSupport.fireMouseDragged(sve);
255        }
256
257        public void mouseMoved(MouseEvent me)
258        {
259            if (! isActive())
260                return;
261
262            Insets insets = getInsets();
263            me.translatePoint(-insets.left, -insets.top);
264
265            SequenceViewerEvent sve =
266                renderer.processMouseEvent(PairwiseSequencePanel.this, me,
267                                           new ArrayList());
268
269            me.translatePoint(insets.left, insets.top);
270            svmSupport.fireMouseMoved(sve);
271        }
272    };
273
274    /**
275     * Creates a new <code>PairwiseSequencePanel</code> with the
276     * default settings (primary sequence direction HORIZONTAL, scale
277     * 10.0 pixels per symbol, symbol translation 0, secondary symbol
278     * translation 0, leading border 0.0, trailing border 0.0, 12
279     * point sanserif font).
280     */
281    public PairwiseSequencePanel()
282    {
283        super();
284
285        // Direction of the primary sequence
286        direction      = HORIZONTAL;
287        scale          = 10.0;
288        translation    = 0;
289        secTranslation = 0;
290
291        leadingBorder  = new SequenceRenderContext.Border();
292        trailingBorder = new SequenceRenderContext.Border();
293
294        leadingBorder.setSize(0.0);
295        trailingBorder.setSize(0.0);
296
297        hints = new RenderingHints(null);
298
299        this.addPropertyChangeListener(propertyListener);
300        this.addMouseListener(mouseListener);
301        this.addMouseMotionListener(mouseMotionListener);
302    }
303
304    /**
305     * <code>getSequence</code> returns the entire
306     * <code>Sequence</code> currently being rendered.
307     *
308     * @return a <code>Sequence</code>.
309     */
310    public Sequence getSequence()
311    {
312        return sequence;
313    }
314
315    /**
316     * <code>setSequence</code> sets the <code>Sequence</code>
317     * to be rendered.
318     *
319     * @param sequence a <code>Sequence</code>.
320     */
321    public void setSequence(Sequence sequence)
322    {
323        Sequence prevSequence = sequence;
324
325        // Remove out listener from the sequence, if necessary
326        if (prevSequence != null)
327            prevSequence.removeChangeListener(layoutListener);
328
329        this.sequence = sequence;
330
331        // Add our listener to the sequence, if necessary
332        if (sequence != null)
333            sequence.addChangeListener(layoutListener);
334
335        firePropertyChange("sequence", prevSequence, sequence);
336        resizeAndValidate();
337    }
338
339    /**
340     * <code>getSecondarySequence</code> returns the entire secondary
341     * <code>Sequence</code> currently being rendered.
342     *
343     * @return a <code>Sequence</code>.
344     */
345    public Sequence getSecondarySequence()
346    {
347        return secSequence;
348    }
349
350    /**
351     * <code>setSecondarySequence</code> sets the secondary
352     * <code>Sequence</code> to be rendered.
353     *
354     * @param sequence a <code>Sequence</code>.
355     */
356    public void setSecondarySequence(Sequence sequence)
357    {
358        Sequence prevSecSequence = secSequence;
359
360        // Remove out listener from the sequence, if necessary
361        if (prevSecSequence != null)
362            prevSecSequence.removeChangeListener(layoutListener);
363
364        secSequence = sequence;
365
366        // Add our listener to the sequence, if necessary
367        if (sequence != null)
368            sequence.addChangeListener(layoutListener);
369
370        firePropertyChange("secSequence", prevSecSequence, sequence);
371        resizeAndValidate();
372    }
373
374    /**
375     * <code>getSymbols</code> returns all of the <code>Symbol</code>s
376     * belonging to the currently rendered <code>Sequence</code>.
377     *
378     * @return a <code>SymbolList</code>.
379     */
380    public SymbolList getSymbols()
381    {
382        return sequence;
383    }
384
385    /**
386     * <code>getSecondarySymbols</code> returns all of the
387     * <code>Symbol</code>s belonging to the currently rendered
388     * secondary <code>Sequence</code>.
389     *
390     * @return a <code>SymbolList</code>.
391     */
392    public SymbolList getSecondarySymbols()
393    {
394        return secSequence;
395    }
396
397    /**
398     * <code>getFeatures</code> returns all of the
399     * <code>Feature</code>s belonging to the currently rendered
400     * <code>Sequence</code>.
401     *
402     * @return a <code>FeatureHolder</code>.
403     */
404    public FeatureHolder getFeatures()
405    {
406        return sequence;
407    }
408
409    /**
410     * <code>getSecondaryFeatures</code> returns all of the
411     * <code>Feature</code>s belonging to the currently rendered
412     * secondary <code>Sequence</code>.
413     *
414     * @return a <code>FeatureHolder</code>.
415     */
416    public FeatureHolder getSecondaryFeatures()
417    {
418        return secSequence;
419    }
420
421    /**
422     * <code>getRange</code> returns a <code>RangeLocation</code>
423     * representing the region of the sequence currently being
424     * rendered. This is calculated from the size of the
425     * <code>PairwiseSequencePanel</code>, the current rendering
426     * translation and the current scale. The value will therefore
427     * change when the <code>PairwiseSequencePanel</code> is resized
428     * or "scrolled" by changing the translation.
429     *
430     * @return a <code>RangeLocation</code>.
431     */
432    public RangeLocation getRange()
433    {
434        int visibleSymbols = getVisibleSymbolCount();
435
436        // This is a fudge as we have to return a RangeLocation, which
437        // can not have start == end
438        if (visibleSymbols == 0)
439            return new RangeLocation(translation + 1,
440                                     translation + 2);
441        else
442            return new RangeLocation(translation + 1, visibleSymbols);
443    }
444
445    /**
446     * <code>getSecondaryRange</code> returns a
447     * <code>RangeLocation</code> representing the region of the
448     * secondary sequence currently being rendered. This is calculated
449     * from the size of the <code>PairwiseSequencePanel</code>, the
450     * current rendering translation and the current scale. The value
451     * will therefore change when the
452     * <code>PairwiseSequencePanel</code> is resized or "scrolled" by
453     * changing the translation.
454     *
455     * @return a <code>RangeLocation</code>.
456     */
457    public RangeLocation getSecondaryRange()
458    {
459        int visibleSecSymbols = getVisibleSecondarySymbolCount();
460
461        // This is a fudge as we have to return a RangeLocation, which
462        // can not have start == end
463        if (visibleSecSymbols == 0)
464            return new RangeLocation(secTranslation + 1,
465                                     secTranslation + 2);
466        else
467            return new RangeLocation(secTranslation + 1, visibleSecSymbols);
468    }
469
470    /**
471     * <code>getDirection</code> returns the direction in which this
472     * context expects the sequence to be rendered - HORIZONTAL or
473     * VERTICAL.
474     *
475     * @return an <code>int</code>.
476     */
477    public int getDirection()
478    {
479        return direction;
480    }
481
482    /**
483     * <code>setDirection</code> sets the direction in which this
484     * context will render the sequence - HORIZONTAL or VERTICAL.
485     *
486     * @param direction an <code>int</code>.
487     *
488     * @exception IllegalArgumentException if an invalid direction is
489     * used.
490     */
491    public void setDirection(int direction)
492        throws IllegalArgumentException
493    {
494        int    prevDirection = direction;
495        int prevSecDirection = secDirection;
496
497         if (direction == HORIZONTAL)
498             secDirection = VERTICAL;
499         else if (direction == VERTICAL)
500             secDirection = HORIZONTAL;
501         else
502             throw new IllegalArgumentException("Direction must be either HORIZONTAL or VERTICAL");
503
504         this.direction = direction;
505
506         firePropertyChange("direction",    prevDirection,    direction);
507         firePropertyChange("secDirection", prevSecDirection, secDirection);
508         resizeAndValidate();
509    }
510
511    /**
512     * <code>getSecondaryDirection</code> returns the direction in
513     * which this context expects the secondary sequence to be
514     * rendered - HORIZONTAL or VERTICAL.
515     *
516     * @return an <code>int</code>.
517     */
518    public int getSecondaryDirection()
519    {
520        return secDirection;
521    }
522
523    /**
524     * <code>getScale</code> returns the scale in pixels per
525     * <code>Symbol</code>.
526     *
527     * @return a <code>double</code>.
528     */
529    public double getScale()
530    {
531        return scale;
532    }
533
534    /**
535     * <code>setScale</code> sets the scale in pixels per
536     * <code>Symbol</code>.
537     *
538     * @param scale a <code>double</code>.
539     */
540    public void setScale(double scale)
541    {
542        double prevScale = this.scale;
543        this.scale = scale;
544
545        firePropertyChange("scale", prevScale, scale);
546        resizeAndValidate();
547    }
548
549    /**
550     * <code>getSymbolTranslation</code> returns the current
551     * translation in <code>Symbol</code>s which will be applied when
552     * rendering. The sequence will be rendered starting at this
553     * translation. Values may be from 0 to the length of the rendered
554     * sequence.
555     *
556     * @return an <code>int</code>.
557     */
558    public int getSymbolTranslation()
559    {
560        return translation;
561    }
562
563    /**
564     * <code>setSymbolTranslation</code> sets the translation in
565     * <code>Symbol</code>s which will be applied when rendering. The
566     * sequence will be rendered starting at that translation. Values
567     * may be from 0 to the length of the rendered sequence.
568     *
569     * @param translation an <code>int</code>.
570     *
571     * @exception IndexOutOfBoundsException if the translation is
572     * greater than the sequence length.
573     */
574    public void setSymbolTranslation(int translation)
575        throws IndexOutOfBoundsException
576    {
577        if (translation >= sequence.length())
578            throw new IndexOutOfBoundsException("Tried to set symbol translation offset equal to or greater than SymbolList length");
579
580        int prevTranslation = this.translation;
581
582        if (hasListeners())
583        {
584            ChangeSupport cs = getChangeSupport(TRANSLATION);
585
586            ChangeEvent ce = new ChangeEvent(this, TRANSLATION);
587
588            cs.firePostChangeEvent(ce);
589            this.translation = translation;
590            cs.firePostChangeEvent(ce);
591        }
592        else
593        {
594            this.translation = translation;
595        }
596
597        firePropertyChange("translation", prevTranslation, translation);
598        resizeAndValidate();
599    }
600
601    /**
602     * <code>getSecondarySymbolTranslation</code> returns the current
603     * translation in <code>Symbol</code>s which will be applied when
604     * rendering. The secondary sequence will be rendered starting at
605     * this translation. Values may be from 0 to the length of the
606     * rendered sequence.
607     *
608     * @return an <code>int</code>.
609     */
610    public int getSecondarySymbolTranslation()
611    {
612        return secTranslation;
613    }
614
615    /**
616     * <code>setSecondarySymbolTranslation</code> sets the translation
617     * in <code>Symbol</code>s which will be applied when
618     * rendering. The secondary sequence will be rendered starting at
619     * that translation. Values may be from 0 to the length of the
620     * rendered sequence.
621     *
622     * @param translation an <code>int</code>.
623     *
624     * @exception IndexOutOfBoundsException if the translation is
625     * greater than the sequence length.
626     */
627    public void setSecondarySymbolTranslation(int translation)
628        throws IndexOutOfBoundsException
629    {
630        if (translation >= secSequence.length())
631            throw new IndexOutOfBoundsException("Tried to set secondary symbol translation offset equal to or greater than SymbolList length");
632
633        int prevSecTranslation = secTranslation;
634
635        if (hasListeners())
636        {
637            ChangeSupport cs = getChangeSupport(TRANSLATION);
638
639            ChangeEvent ce = new ChangeEvent(this, TRANSLATION);
640
641            cs.firePostChangeEvent(ce);
642            secTranslation = translation;
643            cs.firePostChangeEvent(ce);
644        }
645        else
646        {
647            secTranslation = translation;
648        }
649
650        firePropertyChange("secTranslation", prevSecTranslation, translation);
651        resizeAndValidate();
652    }
653
654    /**
655     * <code>getLeadingBorder</code> returns the leading border of the
656     * primary sequence.
657     *
658     * @return a <code>SequenceRenderContext.Border</code>.
659     */
660    public SequenceRenderContext.Border getLeadingBorder()
661    {
662        return leadingBorder;
663    }
664
665    /**
666     * <code>getTrailingBorder</code> returns the trailing border of
667     * the primary sequence.
668     *
669     * @return a <code>SequenceRenderContext.Border</code>.
670     */
671    public SequenceRenderContext.Border getTrailingBorder()
672    {
673        return trailingBorder;
674    }
675
676    /**
677     * <code>getRenderer</code> returns the current
678     * <code>PairwiseSequenceRenderer</code>.
679     *
680     * @return a <code>PairwiseSequenceRenderer</code>.
681     */
682    public PairwiseSequenceRenderer getRenderer()
683    {
684        return renderer;
685    }
686
687    /**
688     * <code>setRenderer</code> sets the current
689     * <code>PairwiseSequenceRenderer</code>.
690     */
691    public void setRenderer(PairwiseSequenceRenderer renderer)
692        throws ChangeVetoException
693    {
694        if (hasListeners())
695        {
696            ChangeSupport cs = getChangeSupport(RENDERER);
697
698            // We are originating a change event so use this
699            // constructor
700            ChangeEvent ce = new ChangeEvent(this, RENDERER,
701                                             renderer, this.renderer);
702
703            synchronized(cs)
704            {
705                cs.firePreChangeEvent(ce);
706                _setRenderer(renderer);
707                cs.firePostChangeEvent(ce);
708            }
709        }
710        else
711        {
712            _setRenderer(renderer);
713        }
714        resizeAndValidate();
715    }
716
717    /**
718     * <code>getRenderingHints</code> returns the
719     * <code>RenderingHints</code> currently being used by the
720     * <code>Graphics2D</code> instances of delegate renderers. If
721     * none is set, the constructor creates one with a null
722     * <code>Map</code>.
723     *
724     * @return a <code>RenderingHints</code>.
725     */
726    public RenderingHints getRenderingHints()
727    {
728        return hints;
729    }
730
731    /**
732     * <code>setRenderingHints</code> sets the
733     * <code>RenderingHints</code> which will be used by the
734     * <code>Graphics2D</code> instances of delegate renderers.
735     *
736     * @param hints a <code>RenderingHints</code>.
737     */
738    public void setRenderingHints(RenderingHints hints)
739    {
740        RenderingHints prevHints = this.hints;
741        this.hints = hints;
742
743        firePropertyChange("hints", prevHints, hints);
744    }
745
746    /**
747     * <code>sequenceToGraphics</code> converts a sequence index
748     * to a graphical position.
749     *
750     * @param sequencePos an <code>int</code>.
751     *
752     * @return a <code>double</code>.
753     */
754    public double sequenceToGraphics(int sequencePos)
755    {
756        return (sequencePos - translation - 1) * scale;
757    }
758
759    /**
760     * <code>secondarySequenceToGraphics</code> converts a sequence
761     * index to a graphical position.
762     *
763     * @param sequencePos an <code>int</code>.
764     *
765     * @return a <code>double</code>.
766     */
767    public double secondarySequenceToGraphics(int sequencePos)
768    {
769        return (sequencePos - secTranslation - 1) * scale;
770    }
771
772    /**
773     * <code>graphicsToSequence</code> converts a graphical position
774     * to a sequence index.
775     *
776     * @param graphicsPos a <code>double</code>.
777     *
778     * @return an <code>int</code>.
779     */
780    public int graphicsToSequence(double graphicsPos)
781    {
782        return ((int) (graphicsPos / scale)) + translation + 1;
783    }
784
785    /**
786     * <code>graphicsToSequence</code> converts a graphical position
787     * to a sequence index.
788     *
789     * @param point a graphic position.
790     *
791     * @return an <code>int</code>.
792     */
793    public int graphicsToSequence(Point2D point)
794    {
795        if (direction == HORIZONTAL)
796            return graphicsToSequence(point.getX());
797        else
798            return graphicsToSequence(point.getY());
799    }
800
801    /**
802     * <code>graphicsToSecondarySequence</code> converts a graphical
803     * position to a secondary sequence index.
804     *
805     * @param graphicsPos a <code>double</code>.
806     *
807     * @return an <code>int</code>.
808     */
809    public int graphicsToSecondarySequence(double graphicsPos)
810    {
811        return ((int) (graphicsPos / scale)) + secTranslation + 1;
812    }
813
814    /**
815     * <code>graphicsToSecondarySequence</code> converts a graphical
816     * position to a secondary sequence index.
817     *
818     * @param point a <code>Point</code>.
819     *
820     * @return an <code>int</code>.
821     */
822    public int graphicsToSecondarySequence(Point point)
823    {
824        if (secDirection == HORIZONTAL)
825            return graphicsToSecondarySequence(point.getX());
826        else
827            return graphicsToSecondarySequence(point.getY());
828    }
829
830    /**
831     * <code>getVisibleSymbolCount</code> returns the
832     * <strong>maximum</strong> number of <code>Symbol</code>s which
833     * can be rendered in the visible area (excluding all borders) of
834     * the <code>PairwiseSequencePanel</code> at the current
835     * scale. Note that if the translation is greater than 0, the
836     * actual number of <code>Symbol</code>s rendered will be less.
837     *
838     * @return an <code>int</code>.
839     */
840    public int getVisibleSymbolCount()
841    {
842        // The Insets
843        Insets insets = getInsets();
844
845        int visible;
846
847        if (direction == HORIZONTAL)
848            visible = getWidth() - insets.left - insets.right;
849        else
850            visible = getHeight() - insets.top - insets.bottom;
851
852        return Math.min(graphicsToSequence(visible), sequence.length());
853    }
854
855    /**
856     * <code>getVisibleSecondarySymbolCount</code> returns the
857     * <strong>maximum</strong> number of secondary
858     * <code>Symbol</code>s which can be rendered in the visible area
859     * (excluding all borders) of the
860     * <code>PairwiseSequencePanel</code> at the current scale. Note
861     * that if the translation is greater than 0, the actual number of
862     * <code>Symbol</code>s rendered will be less.
863     *
864     * @return an <code>int</code>.
865     */
866    public int getVisibleSecondarySymbolCount()
867    {
868        // The Insets
869        Insets insets = getInsets();
870
871        int visible;
872
873        if (secDirection == HORIZONTAL)
874            visible = getWidth() - insets.left - insets.right;
875        else
876            visible = getHeight() - insets.top - insets.bottom;
877
878        return Math.min(graphicsToSecondarySequence(visible),
879                        secSequence.length());
880    }
881
882    public void paintComponent(Graphics g)
883    {
884        if (! isActive())
885            return;
886
887        super.paintComponent(g);
888
889        // Set hints
890        Graphics2D g2 = (Graphics2D) g;
891        g2.addRenderingHints(hints);
892
893        // As we subclass JComponent we have to paint our own
894        // background, but only if we are opaque
895        if (isOpaque())
896        {
897            g2.setPaint(getBackground());
898            g2.fillRect(0, 0, getWidth(), getHeight());
899        }
900
901        // Save current transform and clip
902        AffineTransform prevTransform = g2.getTransform();
903        Shape                prevClip = g2.getClip();
904        Insets                 insets = getInsets();
905
906        Rectangle2D.Double clip = new Rectangle2D.Double();
907
908        clip.x = 0.0;
909        clip.y = 0.0;
910
911        if (direction == HORIZONTAL)
912        {
913            clip.width  = sequenceToGraphics(getVisibleSymbolCount() + 1);
914            clip.height = secondarySequenceToGraphics(getVisibleSecondarySymbolCount() + 1);
915            g2.translate(leadingBorder.getSize() + insets.left, insets.top);
916        }
917        else
918        {
919            clip.width  = secondarySequenceToGraphics(getVisibleSecondarySymbolCount() + 1);
920            clip.height = sequenceToGraphics(getVisibleSymbolCount() + 1);
921            g2.translate(insets.left, leadingBorder.getSize() + insets.top);
922        }
923
924        // Clip and paint
925        g2.clip(clip);
926        renderer.paint(g2, this);
927
928        // Restore
929        g2.setTransform(prevTransform);
930        g2.setClip(prevClip);
931    }
932
933    /**
934     * <code>resizeAndValidate</code> sets the minimum, preferred and
935     * maximum sizes of the component according to the current visible
936     * symbol count.
937     */
938    public void resizeAndValidate()
939    {
940        Dimension d = null;
941
942        if (! isActive())
943        {
944            d = new Dimension(0, 0);
945        }
946        else
947        {
948            double width;
949            double height;
950
951            if (direction == HORIZONTAL)
952            {
953                width  = sequenceToGraphics(getVisibleSymbolCount());
954                height = secondarySequenceToGraphics(getVisibleSecondarySymbolCount());
955            }
956            else
957            {
958                width  = secondarySequenceToGraphics(getVisibleSecondarySymbolCount());
959                height = sequenceToGraphics(getVisibleSymbolCount());
960            }
961
962            d = new Dimension((int) Math.ceil(width),
963                              (int) Math.ceil(height));
964        }
965
966        setMinimumSize(d);
967        setPreferredSize(d);
968        setMaximumSize(d);
969        revalidate();
970    }
971
972    /**
973     * <code>addChangeListener</code> adds a listener for all types of
974     * change.
975     *
976     * @param cl a <code>ChangeListener</code>.
977     */
978    public void addChangeListener(ChangeListener cl)
979    {
980        addChangeListener(cl, ChangeType.UNKNOWN);
981    }
982
983    /**
984     * <code>addChangeListener</code> adds a listener for specific
985     * types of change.
986     *
987     * @param cl a <code>ChangeListener</code>.
988     * @param ct a <code>ChangeType</code>.
989     */
990    public void addChangeListener(ChangeListener cl, ChangeType ct)
991    {
992      ChangeSupport cs = getChangeSupport(ct);
993      cs.addChangeListener(cl, ct);
994    }
995
996    /**
997     * <code>removeChangeListener</code> removes a listener.
998     *
999     * @param cl a <code>ChangeListener</code>.
1000     */
1001    public void removeChangeListener(ChangeListener cl)
1002    {
1003        removeChangeListener(cl, ChangeType.UNKNOWN);
1004    }
1005
1006    /**
1007     * <code>removeChangeListener</code> removes a listener.
1008     *
1009     * @param cl a <code>ChangeListener</code>.
1010     * @param ct a <code>ChangeType</code>.
1011     */
1012    public void removeChangeListener(ChangeListener cl, ChangeType ct)
1013    {
1014      if(hasListeners()) {
1015        ChangeSupport cs = getChangeSupport(ct);
1016        cs.removeChangeListener(cl);
1017      }
1018    }
1019
1020    public boolean isUnchanging(ChangeType ct) {
1021      ChangeSupport cs = getChangeSupport(ct);
1022      return cs.isUnchanging(ct);
1023    }
1024
1025    /**
1026     * <code>addSequenceViewerListener</code> adds a listener for
1027     * mouse click <code>SequenceViewerEvent</code>s.
1028     *
1029     * @param svl a <code>SequenceViewerListener</code>.
1030     */
1031    public void addSequenceViewerListener(SequenceViewerListener svl)
1032    {
1033        svSupport.addSequenceViewerListener(svl);
1034    }
1035
1036    /**
1037     * <code>removeSequenceViewerListener</code> removes a listener
1038     * for mouse click <code>SequenceViewerEvent</code>s.
1039     *
1040     * @param svl a <code>SequenceViewerListener</code>.
1041     */
1042    public void removeSequenceViewerListener(SequenceViewerListener svl)
1043    {
1044        svSupport.removeSequenceViewerListener(svl);
1045    }
1046
1047    /**
1048     * <code>addSequenceViewerMotionListener</code> adds a listener for
1049     * mouse motion <code>SequenceViewerEvent</code>s.
1050     *
1051     * @param svml a <code>SequenceViewerMotionListener</code>.
1052     */
1053    public void addSequenceViewerMotionListener(SequenceViewerMotionListener svml)
1054    {
1055        svmSupport.addSequenceViewerMotionListener(svml);
1056    }
1057
1058    /**
1059     * <code>addSequenceViewerMotionListener</code> removes a listener for
1060     * mouse motion <code>SequenceViewerEvent</code>s.
1061     *
1062     * @param svml a <code>SequenceViewerMotionListener</code>.
1063     */
1064    public void removeSequenceViewerMotionListener(SequenceViewerMotionListener svml)
1065    {
1066        svmSupport.removeSequenceViewerMotionListener(svml);
1067    }
1068
1069    /**
1070     * <code>getChangeSupport</code> lazily instantiates a helper for
1071     * change listeners.
1072     *
1073     * @param ct a <code>ChangeType</code>.
1074     *
1075     * @return a <code>ChangeSupport</code> object.
1076     */
1077    protected ChangeSupport getChangeSupport(ChangeType ct)
1078    {
1079      if(changeSupport != null) {
1080        return changeSupport;
1081      }
1082
1083      synchronized(this) {
1084        if (changeSupport == null) {
1085          changeSupport = new ChangeSupport();
1086        }
1087
1088        return changeSupport;
1089      }
1090    }
1091
1092    /**
1093     * <code>hasListeners</code> returns true if there are active
1094     * listeners for BioJava events.
1095     *
1096     * @return a <code>boolean</code> value.
1097     */
1098    protected boolean hasListeners()
1099    {
1100        return changeSupport != null;
1101    }
1102
1103    /**
1104     * <code>isActive</code> returns true if both the
1105     * <code>Sequence</code>s to be rendered and the
1106     * <code>PairwiseHomologyRenderer</code> are not null.
1107     *
1108     * @return a <code>boolean</code> value.
1109     */
1110    protected boolean isActive()
1111    {
1112        return (sequence != null) && (secSequence != null) &&
1113            (renderer != null);
1114    }
1115
1116    private void _setRenderer(PairwiseSequenceRenderer renderer)
1117    {
1118        // Remove our listeners from the old renderer, if necessary
1119        if (this.renderer != null &&
1120            Changeable.class.isInstance(this.renderer))
1121        {
1122            Changeable c = (Changeable) this.renderer;
1123
1124            c.removeChangeListener(layoutListener,  SequenceRenderContext.LAYOUT);
1125            c.removeChangeListener(repaintListener, SequenceRenderContext.REPAINT);
1126        }
1127
1128        this.renderer = renderer;
1129
1130        // Add our listeners to the new renderer, if necessary
1131        if (renderer != null &&
1132            Changeable.class.isInstance(renderer))
1133        {
1134            Changeable c = (Changeable) renderer;
1135            c.addChangeListener(layoutListener,  SequenceRenderContext.LAYOUT);
1136            c.addChangeListener(repaintListener, SequenceRenderContext.REPAINT);
1137        }
1138    }
1139}