Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.viewmodel.seqfeatures

File FeatureRendererModel.java

 

Coverage histogram

../../../img/srcFileCovDistChart8.png
19% of files have more coverage

Code metrics

178
321
55
2
1,180
812
170
0.53
5.84
27.5
3.09

Classes

Class Line # Actions
FeatureRendererModel 50 317 169 124
0.774134877.4%
FeatureRendererModel.FeatureSettingsBean 56 4 1 0
1.0100%
 

Contributing tests

This file is covered by 56 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.viewmodel.seqfeatures;
22   
23    import jalview.api.AlignViewportI;
24    import jalview.api.FeatureColourI;
25    import jalview.api.FeaturesDisplayedI;
26    import jalview.datamodel.AlignmentI;
27    import jalview.datamodel.SequenceFeature;
28    import jalview.datamodel.SequenceI;
29    import jalview.datamodel.features.FeatureMatcherSetI;
30    import jalview.datamodel.features.SequenceFeatures;
31    import jalview.renderer.seqfeatures.FeatureRenderer;
32    import jalview.schemes.FeatureColour;
33    import jalview.util.ColorUtils;
34   
35    import java.awt.Color;
36    import java.beans.PropertyChangeListener;
37    import java.beans.PropertyChangeSupport;
38    import java.util.ArrayList;
39    import java.util.Arrays;
40    import java.util.Comparator;
41    import java.util.HashMap;
42    import java.util.HashSet;
43    import java.util.Hashtable;
44    import java.util.Iterator;
45    import java.util.List;
46    import java.util.Map;
47    import java.util.Set;
48    import java.util.concurrent.ConcurrentHashMap;
49   
 
50    public abstract class FeatureRendererModel
51    implements jalview.api.FeatureRenderer
52    {
53    /*
54    * a data bean to hold one row of feature settings from the gui
55    */
 
56    public static class FeatureSettingsBean
57    {
58    public final String featureType;
59   
60    public final FeatureColourI featureColour;
61   
62    public final FeatureMatcherSetI filter;
63   
64    public final Boolean show;
65   
 
66  35 toggle public FeatureSettingsBean(String type, FeatureColourI colour,
67    FeatureMatcherSetI theFilter, Boolean isShown)
68    {
69  35 featureType = type;
70  35 featureColour = colour;
71  35 filter = theFilter;
72  35 show = isShown;
73    }
74    }
75   
76    /*
77    * global transparency for feature
78    */
79    protected float transparency = 1.0f;
80   
81    /*
82    * colour scheme for each feature type
83    */
84    protected Map<String, FeatureColourI> featureColours = new ConcurrentHashMap<>();
85   
86    /*
87    * visibility flag for each feature group
88    */
89    protected Map<String, Boolean> featureGroups = new ConcurrentHashMap<>();
90   
91    /*
92    * filters for each feature type
93    */
94    protected Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
95   
96    protected String[] renderOrder;
97   
98    Map<String, Float> featureOrder = null;
99   
100    protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
101    this);
102   
103    protected AlignViewportI av;
104   
 
105  3367 toggle @Override
106    public AlignViewportI getViewport()
107    {
108  3367 return av;
109    }
110   
 
111  2 toggle public FeatureRendererSettings getSettings()
112    {
113  2 return new FeatureRendererSettings(this);
114    }
115   
 
116  51 toggle public void transferSettings(FeatureRendererSettings fr)
117    {
118  51 this.renderOrder = fr.renderOrder;
119  51 this.featureGroups = fr.featureGroups;
120  51 this.featureColours = fr.featureColours;
121  51 this.transparency = fr.transparency;
122  51 this.featureOrder = fr.featureOrder;
123    }
124   
125    /**
126    * update from another feature renderer
127    *
128    * @param fr
129    * settings to copy
130    */
 
131  8 toggle public void transferSettings(jalview.api.FeatureRenderer _fr)
132    {
133  8 FeatureRenderer fr = (FeatureRenderer) _fr;
134  8 FeatureRendererSettings frs = new FeatureRendererSettings(fr);
135  8 this.renderOrder = frs.renderOrder;
136  8 this.featureGroups = frs.featureGroups;
137  8 this.featureColours = frs.featureColours;
138  8 this.featureFilters = frs.featureFilters;
139  8 this.transparency = frs.transparency;
140  8 this.featureOrder = frs.featureOrder;
141  8 if (av != null && av != fr.getViewport())
142    {
143    // copy over the displayed feature settings
144  0 if (_fr.getFeaturesDisplayed() != null)
145    {
146  0 FeaturesDisplayedI fd = getFeaturesDisplayed();
147  0 if (fd == null)
148    {
149  0 setFeaturesDisplayedFrom(_fr.getFeaturesDisplayed());
150    }
151    else
152    {
153  0 synchronized (fd)
154    {
155  0 fd.clear();
156  0 for (String type : _fr.getFeaturesDisplayed()
157    .getVisibleFeatures())
158    {
159  0 fd.setVisible(type);
160    }
161    }
162    }
163    }
164    }
165    }
166   
 
167  0 toggle public void setFeaturesDisplayedFrom(FeaturesDisplayedI featuresDisplayed)
168    {
169  0 av.setFeaturesDisplayed(new FeaturesDisplayed(featuresDisplayed));
170    }
171   
 
172  16 toggle @Override
173    public void setVisible(String featureType)
174    {
175  16 FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
176  16 if (fdi == null)
177    {
178  5 av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
179    }
180  16 if (!fdi.isRegistered(featureType))
181    {
182  16 pushFeatureType(Arrays.asList(new String[] { featureType }));
183    }
184  16 fdi.setVisible(featureType);
185    }
186   
 
187  1 toggle @Override
188    public void setAllVisible(List<String> featureTypes)
189    {
190  1 FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
191  1 if (fdi == null)
192    {
193  1 av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
194    }
195  1 List<String> nft = new ArrayList<>();
196  1 for (String featureType : featureTypes)
197    {
198  1 if (!fdi.isRegistered(featureType))
199    {
200  1 nft.add(featureType);
201    }
202    }
203  1 if (nft.size() > 0)
204    {
205  1 pushFeatureType(nft);
206    }
207  1 fdi.setAllVisible(featureTypes);
208    }
209   
210    /**
211    * push a set of new types onto the render order stack. Note - this is a
212    * direct mechanism rather than the one employed in updateRenderOrder
213    *
214    * @param types
215    */
 
216  17 toggle private void pushFeatureType(List<String> types)
217    {
218   
219  17 int ts = types.size();
220  17 String neworder[] = new String[(renderOrder == null ? 0
221    : renderOrder.length) + ts];
222  17 types.toArray(neworder);
223  17 if (renderOrder != null)
224    {
225  11 System.arraycopy(neworder, 0, neworder, renderOrder.length, ts);
226  11 System.arraycopy(renderOrder, 0, neworder, 0, renderOrder.length);
227    }
228  17 renderOrder = neworder;
229    }
230   
231    protected Map<String, float[][]> minmax = new Hashtable<>();
232   
 
233  21 toggle public Map<String, float[][]> getMinMax()
234    {
235  21 return minmax;
236    }
237   
238    /**
239    * normalise a score against the max/min bounds for the feature type.
240    *
241    * @param sequenceFeature
242    * @return byte[] { signed, normalised signed (-127 to 127) or unsigned
243    * (0-255) value.
244    */
 
245  0 toggle protected final byte[] normaliseScore(SequenceFeature sequenceFeature)
246    {
247  0 float[] mm = minmax.get(sequenceFeature.type)[0];
248  0 final byte[] r = new byte[] { 0, (byte) 255 };
249  0 if (mm != null)
250    {
251  0 if (r[0] != 0 || mm[0] < 0.0)
252    {
253  0 r[0] = 1;
254  0 r[1] = (byte) ((int) 128.0
255    + 127.0 * (sequenceFeature.score / mm[1]));
256    }
257    else
258    {
259  0 r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1]));
260    }
261    }
262  0 return r;
263    }
264   
265    boolean newFeatureAdded = false;
266   
267    boolean findingFeatures = false;
268   
 
269  1059 toggle protected boolean updateFeatures()
270    {
271  1059 if (av.getFeaturesDisplayed() == null || renderOrder == null
272    || newFeatureAdded)
273    {
274  0 findAllFeatures();
275  0 if (av.getFeaturesDisplayed().getVisibleFeatureCount() < 1)
276    {
277  0 return false;
278    }
279    }
280    // TODO: decide if we should check for the visible feature count first
281  1059 return true;
282    }
283   
284    /**
285    * search the alignment for all new features, give them a colour and display
286    * them. Then fires a PropertyChangeEvent on the changeSupport object.
287    *
288    */
 
289  17 toggle protected void findAllFeatures()
290    {
291  17 synchronized (firing)
292    {
293  17 if (firing.equals(Boolean.FALSE))
294    {
295  17 firing = Boolean.TRUE;
296  17 findAllFeatures(true); // add all new features as visible
297  17 changeSupport.firePropertyChange("changeSupport", null, null);
298  17 firing = Boolean.FALSE;
299    }
300    }
301    }
302   
 
303  18 toggle @Override
304    public List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
305    {
306    /*
307    * include features at the position provided their feature type is
308    * displayed, and feature group is null or marked for display
309    */
310  18 List<SequenceFeature> result = new ArrayList<>();
311  18 if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
312    {
313  1 return result;
314    }
315   
316  17 Set<String> visibleFeatures = getFeaturesDisplayed()
317    .getVisibleFeatures();
318  17 String[] visibleTypes = visibleFeatures
319    .toArray(new String[visibleFeatures.size()]);
320  17 List<SequenceFeature> features = sequence.findFeatures(column, column,
321    visibleTypes);
322   
323    /*
324    * include features unless their feature group is not displayed, or
325    * they are hidden (have no colour) based on a filter or colour threshold
326    */
327  17 for (SequenceFeature sf : features)
328    {
329  33 if (!featureGroupNotShown(sf) && getColour(sf) != null)
330    {
331  23 result.add(sf);
332    }
333    }
334  17 return result;
335    }
336   
337    /**
338    * Searches alignment for all features and updates colours
339    *
340    * @param newMadeVisible
341    * if true newly added feature types will be rendered immediately
342    * TODO: check to see if this method should actually be proxied so
343    * repaint events can be propagated by the renderer code
344    */
 
345  62 toggle @Override
346    public synchronized void findAllFeatures(boolean newMadeVisible)
347    {
348  62 newFeatureAdded = false;
349   
350  62 if (findingFeatures)
351    {
352  0 newFeatureAdded = true;
353  0 return;
354    }
355   
356  62 findingFeatures = true;
357  62 if (av.getFeaturesDisplayed() == null)
358    {
359  21 av.setFeaturesDisplayed(new FeaturesDisplayed());
360    }
361  62 FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed();
362   
363  62 Set<String> oldfeatures = new HashSet<>();
364  62 if (renderOrder != null)
365    {
366  97 for (int i = 0; i < renderOrder.length; i++)
367    {
368  71 if (renderOrder[i] != null)
369    {
370  71 oldfeatures.add(renderOrder[i]);
371    }
372    }
373    }
374   
375  62 AlignmentI alignment = av.getAlignment();
376  62 List<String> allfeatures = new ArrayList<>();
377   
378  188 for (int i = 0; i < alignment.getHeight(); i++)
379    {
380  126 SequenceI asq = alignment.getSequenceAt(i);
381  126 for (String group : asq.getFeatures().getFeatureGroups(true))
382    {
383  105 boolean groupDisplayed = true;
384  105 if (group != null)
385    {
386  91 if (featureGroups.containsKey(group))
387    {
388  56 groupDisplayed = featureGroups.get(group);
389    }
390    else
391    {
392  35 groupDisplayed = newMadeVisible;
393  35 featureGroups.put(group, groupDisplayed);
394    }
395    }
396  105 if (groupDisplayed)
397    {
398  104 Set<String> types = asq.getFeatures().getFeatureTypesForGroups(
399    true, group);
400  104 for (String type : types)
401    {
402  150 if (!allfeatures.contains(type)) // or use HashSet and no test?
403    {
404  97 allfeatures.add(type);
405    }
406  150 updateMinMax(asq, type, true); // todo: for all features?
407    }
408    }
409    }
410    }
411   
412    // uncomment to add new features in alphebetical order (but JAL-2575)
413    // Collections.sort(allfeatures, String.CASE_INSENSITIVE_ORDER);
414  62 if (newMadeVisible)
415    {
416  60 for (String type : allfeatures)
417    {
418  87 if (!oldfeatures.contains(type))
419    {
420  56 featuresDisplayed.setVisible(type);
421  56 setOrder(type, 0);
422    }
423    }
424    }
425   
426  62 updateRenderOrder(allfeatures);
427  62 findingFeatures = false;
428    }
429   
430    /**
431    * Updates the global (alignment) min and max values for a feature type from
432    * the score for a sequence, if the score is not NaN. Values are stored
433    * separately for positional and non-positional features.
434    *
435    * @param seq
436    * @param featureType
437    * @param positional
438    */
 
439  150 toggle protected void updateMinMax(SequenceI seq, String featureType,
440    boolean positional)
441    {
442  150 float min = seq.getFeatures().getMinimumScore(featureType, positional);
443  150 if (Float.isNaN(min))
444    {
445  26 return;
446    }
447   
448  124 float max = seq.getFeatures().getMaximumScore(featureType, positional);
449   
450    /*
451    * stored values are
452    * { {positionalMin, positionalMax}, {nonPositionalMin, nonPositionalMax} }
453    */
454  124 if (minmax == null)
455    {
456  0 minmax = new Hashtable<>();
457    }
458  124 synchronized (minmax)
459    {
460  124 float[][] mm = minmax.get(featureType);
461  124 int index = positional ? 0 : 1;
462  124 if (mm == null)
463    {
464  61 mm = new float[][] { null, null };
465  61 minmax.put(featureType, mm);
466    }
467  124 if (mm[index] == null)
468    {
469  61 mm[index] = new float[] { min, max };
470    }
471    else
472    {
473  63 mm[index][0] = Math.min(mm[index][0], min);
474  63 mm[index][1] = Math.max(mm[index][1], max);
475    }
476    }
477    }
478    protected Boolean firing = Boolean.FALSE;
479   
480    /**
481    * replaces the current renderOrder with the unordered features in
482    * allfeatures. The ordering of any types in both renderOrder and allfeatures
483    * is preserved, and all new feature types are rendered on top of the existing
484    * types, in the order given by getOrder or the order given in allFeatures.
485    * Note. this operates directly on the featureOrder hash for efficiency. TODO:
486    * eliminate the float storage for computing/recalling the persistent ordering
487    * New Cability: updates min/max for colourscheme range if its dynamic
488    *
489    * @param allFeatures
490    */
 
491  62 toggle private void updateRenderOrder(List<String> allFeatures)
492    {
493  62 List<String> allfeatures = new ArrayList<>(allFeatures);
494  62 String[] oldRender = renderOrder;
495  62 renderOrder = new String[allfeatures.size()];
496  62 boolean initOrders = (featureOrder == null);
497  62 int opos = 0;
498  62 if (oldRender != null && oldRender.length > 0)
499    {
500  95 for (int j = 0; j < oldRender.length; j++)
501    {
502  71 if (oldRender[j] != null)
503    {
504  71 if (initOrders)
505    {
506  9 setOrder(oldRender[j],
507    (1 - (1 + (float) j) / oldRender.length));
508    }
509  71 if (allfeatures.contains(oldRender[j]))
510    {
511  41 renderOrder[opos++] = oldRender[j]; // existing features always
512    // appear below new features
513  41 allfeatures.remove(oldRender[j]);
514  41 if (minmax != null)
515    {
516  41 float[][] mmrange = minmax.get(oldRender[j]);
517  41 if (mmrange != null)
518    {
519  39 FeatureColourI fc = featureColours.get(oldRender[j]);
520  39 if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()
521    && !fc.isColourByAttribute())
522    {
523  0 fc.updateBounds(mmrange[0][0], mmrange[0][1]);
524    }
525    }
526    }
527    }
528    }
529    }
530    }
531  62 if (allfeatures.size() == 0)
532    {
533    // no new features - leave order unchanged.
534  27 return;
535    }
536  35 int i = allfeatures.size() - 1;
537  35 int iSize = i;
538  35 boolean sort = false;
539  35 String[] newf = new String[allfeatures.size()];
540  35 float[] sortOrder = new float[allfeatures.size()];
541  35 for (String newfeat : allfeatures)
542    {
543  56 newf[i] = newfeat;
544  56 if (minmax != null)
545    {
546    // update from new features minmax if necessary
547  56 float[][] mmrange = minmax.get(newf[i]);
548  56 if (mmrange != null)
549    {
550  47 FeatureColourI fc = featureColours.get(newf[i]);
551  47 if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()
552    && !fc.isColourByAttribute())
553    {
554  0 fc.updateBounds(mmrange[0][0], mmrange[0][1]);
555    }
556    }
557    }
558  56 if (initOrders || !featureOrder.containsKey(newf[i]))
559    {
560  0 int denom = initOrders ? allfeatures.size() : featureOrder.size();
561    // new unordered feature - compute persistent ordering at head of
562    // existing features.
563  0 setOrder(newf[i], i / (float) denom);
564    }
565    // set order from newly found feature from persisted ordering.
566  56 sortOrder[i] = 2 - featureOrder.get(newf[i]).floatValue();
567  56 if (i < iSize)
568    {
569    // only sort if we need to
570  21 sort = sort || sortOrder[i] > sortOrder[i + 1];
571    }
572  56 i--;
573    }
574  35 if (iSize > 1 && sort)
575    {
576  0 jalview.util.QuickSort.sort(sortOrder, newf);
577    }
578  35 sortOrder = null;
579  35 System.arraycopy(newf, 0, renderOrder, opos, newf.length);
580    }
581   
582    /**
583    * get a feature style object for the given type string. Creates a
584    * java.awt.Color for a featureType with no existing colourscheme.
585    *
586    * @param featureType
587    * @return
588    */
 
589  7958 toggle @Override
590    public FeatureColourI getFeatureStyle(String featureType)
591    {
592  7958 FeatureColourI fc = featureColours.get(featureType);
593  7958 if (fc == null)
594    {
595  26 Color col = ColorUtils.createColourFromName(featureType);
596  26 fc = new FeatureColour(col);
597  26 featureColours.put(featureType, fc);
598    }
599  7958 return fc;
600    }
601   
 
602  159 toggle @Override
603    public Color getColour(SequenceFeature feature)
604    {
605  159 FeatureColourI fc = getFeatureStyle(feature.getType());
606  159 return getColor(feature, fc);
607    }
608   
609    /**
610    * Answers true if the feature type is currently selected to be displayed,
611    * else false
612    *
613    * @param type
614    * @return
615    */
 
616  28969 toggle public boolean showFeatureOfType(String type)
617    {
618  28969 return type == null ? false : (av.getFeaturesDisplayed() == null ? true
619    : av.getFeaturesDisplayed().isVisible(type));
620    }
621   
 
622  78 toggle @Override
623    public void setColour(String featureType, FeatureColourI col)
624    {
625  78 featureColours.put(featureType, col);
626    }
627   
 
628  13 toggle @Override
629    public void setTransparency(float value)
630    {
631  13 transparency = value;
632    }
633   
 
634  31 toggle @Override
635    public float getTransparency()
636    {
637  31 return transparency;
638    }
639   
640    /**
641    * analogous to colour - store a normalized ordering for all feature types in
642    * this rendering context.
643    *
644    * @param type
645    * Feature type string
646    * @param position
647    * normalized priority - 0 means always appears on top, 1 means
648    * always last.
649    */
 
650  70 toggle public float setOrder(String type, float position)
651    {
652  70 if (featureOrder == null)
653    {
654  22 featureOrder = new Hashtable<>();
655    }
656  70 featureOrder.put(type, new Float(position));
657  70 return position;
658    }
659   
660    /**
661    * get the global priority (0 (top) to 1 (bottom))
662    *
663    * @param type
664    * @return [0,1] or -1 for a type without a priority
665    */
 
666  226 toggle public float getOrder(String type)
667    {
668  226 if (featureOrder != null)
669    {
670  226 if (featureOrder.containsKey(type))
671    {
672  226 return featureOrder.get(type).floatValue();
673    }
674    }
675  0 return -1;
676    }
677   
 
678  49 toggle @Override
679    public Map<String, FeatureColourI> getFeatureColours()
680    {
681  49 return featureColours;
682    }
683   
684    /**
685    * Replace current ordering with new ordering
686    *
687    * @param data
688    * an array of { Type, Colour, Filter, Boolean }
689    * @return true if any visible features have been reordered, else false
690    */
 
691  12 toggle public boolean setFeaturePriority(FeatureSettingsBean[] data)
692    {
693  12 return setFeaturePriority(data, true);
694    }
695   
696    /**
697    * Sets the priority order for features, with the highest priority (displayed on
698    * top) at the start of the data array
699    *
700    * @param data
701    * an array of { Type, Colour, Filter, Boolean }
702    * @param visibleNew
703    * when true current featureDisplay list will be cleared
704    * @return true if any visible features have been reordered or recoloured, else
705    * false (i.e. no need to repaint)
706    */
 
707  15 toggle public boolean setFeaturePriority(FeatureSettingsBean[] data,
708    boolean visibleNew)
709    {
710    /*
711    * note visible feature ordering and colours before update
712    */
713  15 List<String> visibleFeatures = getDisplayedFeatureTypes();
714  15 Map<String, FeatureColourI> visibleColours = new HashMap<>(
715    getFeatureColours());
716   
717  15 FeaturesDisplayedI av_featuresdisplayed = null;
718  15 if (visibleNew)
719    {
720  ? if ((av_featuresdisplayed = av.getFeaturesDisplayed()) != null)
721    {
722  11 av.getFeaturesDisplayed().clear();
723    }
724    else
725    {
726  1 av.setFeaturesDisplayed(
727    av_featuresdisplayed = new FeaturesDisplayed());
728    }
729    }
730    else
731    {
732  3 av_featuresdisplayed = av.getFeaturesDisplayed();
733    }
734  15 if (data == null)
735    {
736  0 return false;
737    }
738    // The feature table will display high priority
739    // features at the top, but these are the ones
740    // we need to render last, so invert the data
741  15 renderOrder = new String[data.length];
742   
743  15 if (data.length > 0)
744    {
745  52 for (int i = 0; i < data.length; i++)
746    {
747  37 String type = data[i].featureType;
748  37 setColour(type, data[i].featureColour);
749  37 if (data[i].show)
750    {
751  29 av_featuresdisplayed.setVisible(type);
752    }
753   
754  37 renderOrder[data.length - i - 1] = type;
755    }
756    }
757   
758    /*
759    * get the new visible ordering and return true if it has changed
760    * order or any colour has changed
761    */
762  15 List<String> reorderedVisibleFeatures = getDisplayedFeatureTypes();
763  15 if (!visibleFeatures.equals(reorderedVisibleFeatures))
764    {
765    /*
766    * the list of ordered visible features has changed
767    */
768  10 return true;
769    }
770   
771    /*
772    * return true if any feature colour has changed
773    */
774  5 for (String feature : visibleFeatures)
775    {
776  17 if (visibleColours.get(feature) != getFeatureStyle(feature))
777    {
778  0 return true;
779    }
780    }
781  5 return false;
782    }
783   
784    /**
785    * @param listener
786    * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
787    */
 
788  1 toggle public void addPropertyChangeListener(PropertyChangeListener listener)
789    {
790  1 changeSupport.addPropertyChangeListener(listener);
791    }
792   
793    /**
794    * @param listener
795    * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
796    */
 
797  0 toggle public void removePropertyChangeListener(PropertyChangeListener listener)
798    {
799  0 changeSupport.removePropertyChangeListener(listener);
800    }
801   
 
802  1 toggle public Set<String> getAllFeatureColours()
803    {
804  1 return featureColours.keySet();
805    }
806   
 
807  15 toggle public void clearRenderOrder()
808    {
809  15 renderOrder = null;
810    }
811   
 
812  44 toggle public boolean hasRenderOrder()
813    {
814  44 return renderOrder != null;
815    }
816   
817    /**
818    * Returns feature types in ordering of rendering, where last means on top
819    */
 
820  71 toggle public List<String> getRenderOrder()
821    {
822  71 if (renderOrder == null)
823    {
824  1 return Arrays.asList(new String[] {});
825    }
826  70 return Arrays.asList(renderOrder);
827    }
828   
 
829  2 toggle public int getFeatureGroupsSize()
830    {
831  2 return featureGroups != null ? 0 : featureGroups.size();
832    }
833   
 
834  24 toggle @Override
835    public List<String> getFeatureGroups()
836    {
837    // conflict between applet and desktop - featureGroups returns the map in
838    // the desktop featureRenderer
839  24 return (featureGroups == null) ? Arrays.asList(new String[0])
840    : Arrays.asList(featureGroups.keySet().toArray(new String[0]));
841    }
842   
 
843  36 toggle public boolean checkGroupVisibility(String group,
844    boolean newGroupsVisible)
845    {
846  36 if (featureGroups == null)
847    {
848    // then an exception happens next..
849    }
850  36 if (featureGroups.containsKey(group))
851    {
852  36 return featureGroups.get(group).booleanValue();
853    }
854  0 if (newGroupsVisible)
855    {
856  0 featureGroups.put(group, new Boolean(true));
857  0 return true;
858    }
859  0 return false;
860    }
861   
862    /**
863    * get visible or invisible groups
864    *
865    * @param visible
866    * true to return visible groups, false to return hidden ones.
867    * @return list of groups
868    */
 
869  15 toggle @Override
870    public List<String> getGroups(boolean visible)
871    {
872  15 if (featureGroups != null)
873    {
874  15 List<String> gp = new ArrayList<>();
875   
876  15 for (String grp : featureGroups.keySet())
877    {
878  55 Boolean state = featureGroups.get(grp);
879  55 if (state.booleanValue() == visible)
880    {
881  0 gp.add(grp);
882    }
883    }
884  15 return gp;
885    }
886  0 return null;
887    }
888   
 
889  18 toggle @Override
890    public void setGroupVisibility(String group, boolean visible)
891    {
892  18 featureGroups.put(group, new Boolean(visible));
893    }
894   
 
895  0 toggle @Override
896    public void setGroupVisibility(List<String> toset, boolean visible)
897    {
898  0 if (toset != null && toset.size() > 0 && featureGroups != null)
899    {
900  0 boolean rdrw = false;
901  0 for (String gst : toset)
902    {
903  0 Boolean st = featureGroups.get(gst);
904  0 featureGroups.put(gst, new Boolean(visible));
905  0 if (st != null)
906    {
907  0 rdrw = rdrw || (visible != st.booleanValue());
908    }
909    }
910  0 if (rdrw)
911    {
912    // set local flag indicating redraw needed ?
913    }
914    }
915    }
916   
 
917  5 toggle @Override
918    public Map<String, FeatureColourI> getDisplayedFeatureCols()
919    {
920  5 Map<String, FeatureColourI> fcols = new Hashtable<>();
921  5 if (getViewport().getFeaturesDisplayed() == null)
922    {
923  1 return fcols;
924    }
925  4 Set<String> features = getViewport().getFeaturesDisplayed()
926    .getVisibleFeatures();
927  4 for (String feature : features)
928    {
929  10 fcols.put(feature, getFeatureStyle(feature));
930    }
931  4 return fcols;
932    }
933   
 
934  202 toggle @Override
935    public FeaturesDisplayedI getFeaturesDisplayed()
936    {
937  202 return av.getFeaturesDisplayed();
938    }
939   
940    /**
941    * Returns a (possibly empty) list of visible feature types, in render order
942    * (last is on top)
943    */
 
944  42 toggle @Override
945    public List<String> getDisplayedFeatureTypes()
946    {
947  42 List<String> typ = getRenderOrder();
948  42 List<String> displayed = new ArrayList<>();
949  42 FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed();
950  42 if (feature_disp != null)
951    {
952  41 synchronized (feature_disp)
953    {
954  41 for (String type : typ)
955    {
956  109 if (feature_disp.isVisible(type))
957    {
958  95 displayed.add(type);
959    }
960    }
961    }
962    }
963  42 return displayed;
964    }
965   
 
966  0 toggle @Override
967    public List<String> getDisplayedFeatureGroups()
968    {
969  0 List<String> _gps = new ArrayList<>();
970  0 for (String gp : getFeatureGroups())
971    {
972  0 if (checkGroupVisibility(gp, false))
973    {
974  0 _gps.add(gp);
975    }
976    }
977  0 return _gps;
978    }
979   
980    /**
981    * Answers true if the feature belongs to a feature group which is not
982    * currently displayed, else false
983    *
984    * @param sequenceFeature
985    * @return
986    */
 
987  26176 toggle protected boolean featureGroupNotShown(final SequenceFeature sequenceFeature)
988    {
989  26176 return featureGroups != null
990    && sequenceFeature.featureGroup != null
991    && sequenceFeature.featureGroup.length() != 0
992    && featureGroups.containsKey(sequenceFeature.featureGroup)
993    && !featureGroups.get(sequenceFeature.featureGroup)
994    .booleanValue();
995    }
996   
997    /**
998    * {@inheritDoc}
999    */
 
1000  60 toggle @Override
1001    public List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence,
1002    int resNo)
1003    {
1004  60 List<SequenceFeature> result = new ArrayList<>();
1005  60 if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
1006    {
1007  0 return result;
1008    }
1009   
1010    /*
1011    * include features at the position provided their feature type is
1012    * displayed, and feature group is null or the empty string
1013    * or marked for display
1014    */
1015  60 Set<String> visibleFeatures = getFeaturesDisplayed()
1016    .getVisibleFeatures();
1017  60 String[] visibleTypes = visibleFeatures
1018    .toArray(new String[visibleFeatures.size()]);
1019  60 List<SequenceFeature> features = sequence.getFeatures().findFeatures(
1020    resNo, resNo, visibleTypes);
1021   
1022  60 for (SequenceFeature sf : features)
1023    {
1024  70 if (!featureGroupNotShown(sf) && getColour(sf) != null)
1025    {
1026  70 result.add(sf);
1027    }
1028    }
1029  60 return result;
1030    }
1031   
1032    /**
1033    * Removes from the list of features any that duplicate the location of a
1034    * feature of the same type. Should be used only for features of the same,
1035    * simple, feature colour (which normally implies the same feature type). Does
1036    * not check visibility settings for feature type or feature group. No
1037    * filtering is done if transparency, or any feature filters, are in force.
1038    *
1039    * @param features
1040    */
 
1041  7538 toggle public void filterFeaturesForDisplay(List<SequenceFeature> features)
1042    {
1043    /*
1044    * don't remove 'redundant' features if
1045    * - transparency is applied (feature count affects depth of feature colour)
1046    * - filters are applied (not all features may be displayable)
1047    */
1048  7538 if (features.isEmpty() || transparency != 1f
1049    || !featureFilters.isEmpty())
1050    {
1051  4769 return;
1052    }
1053   
1054  2769 SequenceFeatures.sortFeatures(features, true);
1055  2769 SequenceFeature lastFeature = null;
1056   
1057  2769 Iterator<SequenceFeature> it = features.iterator();
1058  28664 while (it.hasNext())
1059    {
1060  25895 SequenceFeature sf = it.next();
1061   
1062    /*
1063    * a feature is redundant for rendering purposes if it has the
1064    * same extent as another (so would just redraw the same colour);
1065    * (checking type and isContactFeature as a fail-safe here, although
1066    * currently they are guaranteed to match in this context)
1067    */
1068  25895 if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
1069    && sf.getEnd() == lastFeature.getEnd()
1070    && sf.isContactFeature() == lastFeature.isContactFeature()
1071    && sf.getType().equals(lastFeature.getType()))
1072    {
1073  4 it.remove();
1074    }
1075  25895 lastFeature = sf;
1076    }
1077    }
1078   
 
1079  3 toggle @Override
1080    public Map<String, FeatureMatcherSetI> getFeatureFilters()
1081    {
1082  3 return featureFilters;
1083    }
1084   
 
1085  0 toggle @Override
1086    public void setFeatureFilters(Map<String, FeatureMatcherSetI> filters)
1087    {
1088  0 featureFilters = filters;
1089    }
1090   
 
1091  234 toggle @Override
1092    public FeatureMatcherSetI getFeatureFilter(String featureType)
1093    {
1094  234 return featureFilters.get(featureType);
1095    }
1096   
 
1097  20 toggle @Override
1098    public void setFeatureFilter(String featureType, FeatureMatcherSetI filter)
1099    {
1100  20 if (filter == null || filter.isEmpty())
1101    {
1102  0 featureFilters.remove(featureType);
1103    }
1104    else
1105    {
1106  20 featureFilters.put(featureType, filter);
1107    }
1108    }
1109   
1110    /**
1111    * Answers the colour for the feature, or null if the feature is excluded by
1112    * feature group visibility, by filters, or by colour threshold settings. This
1113    * method does not take feature visibility into account.
1114    *
1115    * @param sf
1116    * @param fc
1117    * @return
1118    */
 
1119  26050 toggle public Color getColor(SequenceFeature sf, FeatureColourI fc)
1120    {
1121    /*
1122    * is the feature group displayed?
1123    */
1124  26050 if (featureGroupNotShown(sf))
1125    {
1126  1 return null;
1127    }
1128   
1129    /*
1130    * does the feature pass filters?
1131    */
1132  26049 if (!featureMatchesFilters(sf))
1133    {
1134  6 return null;
1135    }
1136   
1137  26043 return fc.getColor(sf);
1138    }
1139   
1140    /**
1141    * Answers true if there no are filters defined for the feature type, or this
1142    * feature matches the filters. Answers false if the feature fails to match
1143    * filters.
1144    *
1145    * @param sf
1146    * @return
1147    */
 
1148  26049 toggle protected boolean featureMatchesFilters(SequenceFeature sf)
1149    {
1150  26049 FeatureMatcherSetI filter = featureFilters.get(sf.getType());
1151  26049 return filter == null ? true : filter.matches(sf);
1152    }
1153   
1154    /**
1155    * Answers true unless the specified group is set to hidden. Defaults to true
1156    * if group visibility is not set.
1157    *
1158    * @param group
1159    * @return
1160    */
 
1161  0 toggle public boolean isGroupVisible(String group)
1162    {
1163  0 if (!featureGroups.containsKey(group))
1164    {
1165  0 return true;
1166    }
1167  0 return featureGroups.get(group);
1168    }
1169   
1170    /**
1171    * Orders features in render precedence (last in order is last to render, so
1172    * displayed on top of other features)
1173    *
1174    * @param order
1175    */
 
1176  10 toggle public void orderFeatures(Comparator<String> order)
1177    {
1178  10 Arrays.sort(renderOrder, order);
1179    }
1180    }