Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.viewmodel.seqfeatures

File FeatureRendererModel.java

 

Coverage histogram

../../../img/srcFileCovDistChart9.png
12% of files have more coverage

Code metrics

208
377
57
2
1,324
928
189
0.5
6.61
28.5
3.32

Classes

Class Line # Actions
FeatureRendererModel 57 373 188
0.814756781.5%
FeatureRendererModel.FeatureSettingsBean 63 4 1
1.0100%
 

Contributing tests

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