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 */
021package org.biojava.bio.gui.sequence;
022
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029
030import org.biojava.bio.seq.ByLocationMinMaxFeatureComparator;
031import org.biojava.bio.seq.Feature;
032import org.biojava.bio.seq.FeatureFilter;
033import org.biojava.bio.seq.FeatureHolder;
034import org.biojava.utils.ChangeEvent;
035import org.biojava.utils.ChangeSupport;
036import org.biojava.utils.ChangeType;
037import org.biojava.utils.ChangeVetoException;
038
039
040/**
041 * A SequenceRenderer that renders a set of Features that match a FeatureFilter in such a way that
042 * they do not overlap in the display.
043 *
044 * @author Mark Southern
045 * @since 1.5
046 */
047public abstract class AbstractPeptideDigestRenderer extends MultiLineRenderer
048{
049    public static final ChangeType DIGEST = new ChangeType("The peptide digest has changed",
050        "org.biojava.bio.gui.sequence.AbstractPeptideDigestRenderer", "DIGEST",
051        SequenceRenderContext.REPAINT
052    );
053    public static final String LANE = "Lane";
054    private FeatureSource source;
055    private FeatureFilter digestFilter;
056    private Map laneMap = new HashMap();
057    private int laneCount = 0;
058    private int distanceBetween = 0;
059    
060    public AbstractPeptideDigestRenderer()
061    {
062        super();
063    }
064
065    public AbstractPeptideDigestRenderer(FeatureSource source)
066    {
067        this();
068        setFeatureSource(source);
069    }
070
071    public AbstractPeptideDigestRenderer(FeatureSource source, FeatureFilter filter)
072    {
073        this(source);
074        setFilter(filter);
075    }
076    
077    public AbstractPeptideDigestRenderer(FeatureSource source, FeatureFilter filter, int distanceBetweenFeatures)
078    {
079        this(source,filter);
080        setDistanceBetweenFeatures(distanceBetweenFeatures);
081    }
082    
083    public void setFeatureSource(FeatureSource source)
084    {
085        this.source = source;
086    }
087    
088    public FeatureSource getFeatureSource()
089    {
090        return source;
091    }
092
093    public FeatureFilter getFilter()
094    {
095        return digestFilter;
096    }
097
098    public void setFilter(FeatureFilter filter)
099    {
100        digestFilter = filter;
101    }
102
103    /*
104     * Sets the space between rendered features. Increase for greater visibility. 
105     */
106    public void setDistanceBetweenFeatures(int d)
107    {
108        distanceBetween = d;
109    }
110
111    public int getDistanceBetweenFeatures()
112    {
113        return distanceBetween;
114    }
115
116    public void sortPeptidesIntoLanes() throws ChangeVetoException
117    {
118        if (hasListeners(DIGEST))
119        {
120            ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);
121
122            synchronized (cs)
123            {
124                ChangeEvent ce = new ChangeEvent(this, DIGEST);
125                cs.firePreChangeEvent(ce);
126                doSortPeptides();
127                doRefreshRenderers();
128                cs.firePostChangeEvent(ce);
129            }
130        }
131        else
132        {
133            doSortPeptides();
134            doRefreshRenderers();
135        }
136    }
137
138    protected void doRefreshRenderers() throws ChangeVetoException
139    {
140        super.clearRenderers();
141        // resort peptide features into new lanes
142        for (int j = 1; j <= laneCount; j++)
143        {
144            //logger.debug("Adding renderers for lane " + j);
145            FeatureFilter ffilt = new FeatureFilter.And(getFilter(), new LaneFeatureFilter(j));
146            FeatureBlockSequenceRenderer block = new FeatureBlockSequenceRenderer();
147            block.setFeatureRenderer(createRenderer(j));
148            PaddingRenderer pad = new PaddingRenderer();
149            pad.setPadding(1);
150            pad.setRenderer(new FilteringRenderer(block, ffilt, true));
151            addRenderer(pad);
152        }
153    }
154    
155    /*
156     * Method used to return the given FeatureRenderer for a given lane in the display.
157     */
158    public abstract FeatureRenderer createRenderer(int lane);
159    
160    protected void doSortPeptides()
161    {
162        // clear existing stored features
163        laneMap.clear();
164
165        //logger.debug("Feature Filter = " + getFilter());
166        FeatureHolder fh = source.getFeatureHolder().filter(getFilter());
167        List ranges = new LinkedList();
168
169        for (Iterator i = fh.features(); i.hasNext();)
170        {
171            ranges.add(( Feature ) i.next());
172        }
173
174        Collections.sort(ranges, new ByLocationMinMaxFeatureComparator());
175
176        Integer lane_id = new Integer(1);
177        int i = 0;
178        int pos = 0;
179
180        while (ranges.size() > 0)
181        {
182            /*//logger.info("i=" + i + "\tpos=" + pos + "\tlane_id=" + lane_id + "\tsize=" +
183               ranges.size()
184               );
185             */
186            Feature f = ( Feature ) ranges.get(i);
187
188            //logger.info("\tloc=" + f.getLocation());
189            if (f.getLocation().getMin() > pos)
190            {
191                //logger.info("Adding location " + f.getLocation() + " to lane " + lane_id);
192                pos = f.getLocation().getMax() + distanceBetween; // +1 so there are no adjoining peptides on a track
193
194                // we can make distanceBetween 0 if we can differenciate between adjoining Features with the specific SequenceRenderer implementation
195                ranges.remove(i);
196                laneMap.put(f, lane_id);
197            } else
198            {
199                i++;
200            }
201
202            if (i >= ranges.size())
203            {
204                i = 0;
205                pos = 0;
206                lane_id = new Integer(lane_id.intValue() + 1);
207                //logger.info("RESET:\t" + i + "\t" + pos + "\t" + lane_id + "\t" + ranges.size());
208            }
209        }
210        laneCount = lane_id.intValue();
211    }
212
213    private class LaneFeatureFilter implements FeatureFilter
214    {
215        private int lane;
216
217        public LaneFeatureFilter(int lane)
218        {
219            this.lane = lane;
220        }
221
222        public boolean accept(Feature f)
223        {
224            Integer i = ( Integer ) laneMap.get(f);
225            return (i != null) && (i.intValue() == lane);
226        }
227    }
228}