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.Rectangle2D;
028import java.util.Iterator;
029import java.util.List;
030
031import org.biojava.bio.seq.FeatureHolder;
032
033/**
034 * <code>LayeredRenderer</code> handles the lane offsets for
035 * <code>MultiLineRender</code>s. For each successive lane it
036 * translates the <code>Graphics2D</code> perpendicular to the
037 * sequence rendering direction by an amount equal to the value
038 * returned by the <code>getDepth()</code> method of that lane's
039 * renderer.
040 *
041 * @author Matthew Pocock
042 * @author Keith James
043 * @since 1.1
044 */
045public class LayeredRenderer {
046
047    /**
048     * Static <code>LayeredRenderer</code> <code>INSTANCE</code> used
049     * by <code>MultiLineRenderer</code>s.
050     */
051    public static final LayeredRenderer INSTANCE = new LayeredRenderer();
052
053    /**
054     * <code>getDepth</code> returns the total depth of a list of
055     * <code>SequenceRenderer</code>s.
056     *
057     * @param srcL a <code>List</code> of
058     * <code>SequenceRenderContext</code>s.
059     * @param renderers a <code>List</code> of
060     * <code>SequenceRenderer</code>s.
061     *
062     * @return a <code>double</code>.
063     */
064    public double getDepth(List srcL, List renderers) {
065        if (srcL.size() != renderers.size()) {
066            throw new IllegalArgumentException("srcL and renderers must be the same size: " +
067                                               srcL.size() + ":" + renderers.size());
068        }
069
070        double depth = 0.0;
071        Iterator srcI = srcL.iterator();
072        Iterator i = renderers.iterator();
073
074        while (srcI.hasNext() && i.hasNext()) {
075            SequenceRenderContext src = (SequenceRenderContext) srcI.next();
076            SequenceRenderer sRend = (SequenceRenderer) i.next();            
077            if(sRend instanceof OverlayMarker){
078                depth += 0.0; // maybe just do nothing here
079            }else {
080                depth += sRend.getDepth(src);                   
081            }
082        }
083        return depth;
084    }
085
086    /**
087     * <code>getMinimumLeader</code> returns the maximum value of
088     * getMinimumLeader() for a list of <code>SequenceRenderer</code>s.
089     *
090     * @param srcL a <code>List</code> of
091     * <code>SequenceRenderContext</code>s.
092     * @param renderers a <code>List</code> of
093     * <code>SequenceRenderer</code>s.
094     *
095     * @return a <code>double</code>.
096     */
097    public double getMinimumLeader(List srcL, List renderers) {
098        if (srcL.size() != renderers.size()) {
099            throw new IllegalArgumentException("srcL and renderers must be the same size: " +
100                                               srcL.size() + ":" + renderers.size());
101        }
102
103        double max = 0.0;
104        Iterator srcI = srcL.iterator();
105        Iterator i = renderers.iterator();
106
107        while (srcI.hasNext() && i.hasNext()) {
108            SequenceRenderContext src = (SequenceRenderContext) srcI.next();
109            SequenceRenderer sRend = (SequenceRenderer) i.next();
110            max = Math.max(max, sRend.getMinimumLeader(src));
111        }
112        return max;
113    }
114
115    /**
116     * <code>getMinimumTrailer</code> returns the maximum value of
117     * getMinimumTrailer() for a list of <code>SequenceRenderer</code>s.
118     *
119     * @param srcL a <code>List</code> of
120     * <code>SequenceRenderContext</code>s.
121     * @param renderers a <code>List</code> of
122     * <code>SequenceRenderer</code>s.
123     *
124     * @return a <code>double</code>.
125     */
126    public double getMinimumTrailer(List srcL, List renderers) {
127        if (srcL.size() != renderers.size()) {
128            throw new IllegalArgumentException("srcL and renderers must be the same size: " +
129                                               srcL.size() + ":" + renderers.size());
130        }
131
132        double max = 0.0;
133        Iterator srcI = srcL.iterator();
134        Iterator i = renderers.iterator();
135
136        while (srcI.hasNext() && i.hasNext()) {
137            SequenceRenderContext src = (SequenceRenderContext) srcI.next();
138            SequenceRenderer sRend = (SequenceRenderer) i.next();
139            max = Math.max(max, sRend.getMinimumTrailer(src));
140        }
141        return max;
142    }
143
144    public void paint(Graphics2D g, List srcL, List renderers) {
145        if (srcL.size() != renderers.size()) {
146            throw new IllegalArgumentException("srcL and renderers must be the same size: " +
147                                               srcL.size() + ":" + renderers.size());
148        }
149
150        // Offset perpendicular to sequence rendering direction
151        double offset = 0.0;
152        // Don't know what this is
153        double allocatedOffset = 0.0;
154
155        Iterator srcI = srcL.iterator();
156        Iterator i = renderers.iterator();
157
158        // New clipping rectangle
159        Rectangle2D clip = new Rectangle2D.Double();
160
161        while (srcI.hasNext() && i.hasNext()) {
162            SequenceRenderContext src = (SequenceRenderContext) srcI.next();
163            SequenceRenderer sRend = (SequenceRenderer) i.next();
164            int dir = src.getDirection();
165            double depth = sRend.getDepth(src);
166
167            // Sequence range should be inclusive of the min/max
168            // coordinates for sequenceToGraphics() so we use
169            // src.getRange().getMax() + 1
170            double minP = src.sequenceToGraphics(src.getRange().getMin()) -
171                sRend.getMinimumLeader(src);
172            double maxP = src.sequenceToGraphics(src.getRange().getMax() + 1) +
173                sRend.getMinimumTrailer(src);
174
175            // Added +1 to these as the outer edge of features was
176            // being clipped off
177            if (dir == SequenceRenderContext.HORIZONTAL) {
178                clip.setFrame(minP, 0.0, maxP - minP + 1, depth + 1);
179                g.translate(0.0, offset);
180            } else {
181                clip.setFrame(0.0, minP, depth + 1, maxP - minP + 1);
182                g.translate(offset, 0.0);
183            }
184
185            Shape oldClip = g.getClip();
186            g.clip(clip);
187            sRend.paint(g, src);
188            g.setClip(oldClip);
189
190            if (dir == SequenceRenderContext.HORIZONTAL) {
191                g.translate(0.0, -offset);
192            } else {
193                g.translate(-offset, 0.0);
194            }
195
196            if (sRend instanceof OverlayMarker)  {
197                // overlay, just record maximum allocation
198                allocatedOffset = Math.max(allocatedOffset, sRend.getDepth(src));
199            } else {
200                // non-overlaid: apply all relevant offsets
201                offset += Math.max(sRend.getDepth(src), allocatedOffset);
202                allocatedOffset = 0.0;  // clear it as it is now applied.
203            }
204        }
205    }
206
207    public SequenceViewerEvent processMouseEvent(List srcL, MouseEvent me,
208                                                 List path, List renderers) {
209        if (srcL.size() != renderers.size()) {
210            throw new IllegalArgumentException("srcL and renderers must be the same size: " +
211                                               srcL.size() + ":" + renderers.size());
212        }
213
214        // Offset perpendicular to sequence rendering direction
215        double offset = 0.0;
216
217        Iterator srcI = srcL.iterator();
218        Iterator i = renderers.iterator();
219
220        SequenceViewerEvent sve = null;
221        
222        while (srcI.hasNext() && i.hasNext()) {
223            SequenceRenderContext src = (SequenceRenderContext) srcI.next();
224            SequenceRenderer sRend = (SequenceRenderer) i.next();
225            double depth = sRend.getDepth(src);
226
227            SequenceViewerEvent thisSve = null;
228            if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
229                if ((me.getY() >= offset) && (me.getY() < offset + depth)) {
230                    me.translatePoint(0, (int) -offset);
231                    thisSve = sRend.processMouseEvent(src, me, path);
232                    me.translatePoint(0, (int) +offset);
233                }
234            } else {
235                if ((me.getX() >= offset) && (me.getX() < offset + depth)) {
236                    me.translatePoint((int) -offset, 0);
237                    thisSve = sRend.processMouseEvent(src, me, path);
238                    me.translatePoint((int) +offset, 0);
239                }
240            }
241
242            if (thisSve != null) {
243                if (sve == null) {
244                    sve = thisSve;
245                } else if (thisSve.getTarget() instanceof FeatureHolder &&
246                           ((FeatureHolder) thisSve.getTarget()).countFeatures() > 0) 
247                {
248                    // features trump anything else
249                    sve = thisSve;
250                }
251            }
252
253            if (! (sRend instanceof OverlayMarker)) offset += depth;
254        }
255        return sve;
256    }
257}
258