JAL-1479 minor refactor and housekeeping
[jalview.git] / src / jalview / viewmodel / seqfeatures / FeatureRendererModel.java
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.renderer.seqfeatures.FeatureRenderer;
30 import jalview.schemes.FeatureColour;
31 import jalview.schemes.UserColourScheme;
32 import jalview.viewmodel.AlignmentViewport;
33
34 import java.awt.Color;
35 import java.beans.PropertyChangeListener;
36 import java.beans.PropertyChangeSupport;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.HashMap;
40 import java.util.Hashtable;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.concurrent.ConcurrentHashMap;
46
47 public abstract class FeatureRendererModel implements
48         jalview.api.FeatureRenderer
49 {
50
51   /**
52    * global transparency for feature
53    */
54   protected float transparency = 1.0f;
55
56   protected Map<String, FeatureColourI> featureColours = new ConcurrentHashMap<String, FeatureColourI>();
57
58   protected Map<String, Boolean> featureGroups = new ConcurrentHashMap<String, Boolean>();
59
60   protected String[] renderOrder;
61
62   Map<String, Float> featureOrder = null;
63
64   protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
65           this);
66
67   protected AlignmentViewport av;
68
69   @Override
70   public AlignViewportI getViewport()
71   {
72     return av;
73   }
74
75   public FeatureRendererSettings getSettings()
76   {
77     return new FeatureRendererSettings(this);
78   }
79
80   public void transferSettings(FeatureRendererSettings fr)
81   {
82     this.renderOrder = fr.renderOrder;
83     this.featureGroups = fr.featureGroups;
84     this.featureColours = fr.featureColours;
85     this.transparency = fr.transparency;
86     this.featureOrder = fr.featureOrder;
87   }
88
89   /**
90    * update from another feature renderer
91    * 
92    * @param fr
93    *          settings to copy
94    */
95   public void transferSettings(jalview.api.FeatureRenderer _fr)
96   {
97     FeatureRenderer fr = (FeatureRenderer) _fr;
98     FeatureRendererSettings frs = new FeatureRendererSettings(fr);
99     this.renderOrder = frs.renderOrder;
100     this.featureGroups = frs.featureGroups;
101     this.featureColours = frs.featureColours;
102     this.transparency = frs.transparency;
103     this.featureOrder = frs.featureOrder;
104     if (av != null && av != fr.getViewport())
105     {
106       // copy over the displayed feature settings
107       if (_fr.getFeaturesDisplayed() != null)
108       {
109         FeaturesDisplayedI fd = getFeaturesDisplayed();
110         if (fd == null)
111         {
112           setFeaturesDisplayedFrom(_fr.getFeaturesDisplayed());
113         }
114         else
115         {
116           synchronized (fd)
117           {
118             fd.clear();
119             java.util.Iterator<String> fdisp = _fr.getFeaturesDisplayed()
120                     .getVisibleFeatures();
121             while (fdisp.hasNext())
122             {
123               fd.setVisible(fdisp.next());
124             }
125           }
126         }
127       }
128     }
129   }
130
131   public void setFeaturesDisplayedFrom(FeaturesDisplayedI featuresDisplayed)
132   {
133     av.setFeaturesDisplayed(new FeaturesDisplayed(featuresDisplayed));
134   }
135
136   @Override
137   public void setVisible(String featureType)
138   {
139     FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
140     if (fdi == null)
141     {
142       av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
143     }
144     if (!fdi.isRegistered(featureType))
145     {
146       pushFeatureType(Arrays.asList(new String[] { featureType }));
147     }
148     fdi.setVisible(featureType);
149   }
150
151   @Override
152   public void setAllVisible(List<String> featureTypes)
153   {
154     FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
155     if (fdi == null)
156     {
157       av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
158     }
159     List<String> nft = new ArrayList<String>();
160     for (String featureType : featureTypes)
161     {
162       if (!fdi.isRegistered(featureType))
163       {
164         nft.add(featureType);
165       }
166     }
167     if (nft.size() > 0)
168     {
169       pushFeatureType(nft);
170     }
171     fdi.setAllVisible(featureTypes);
172   }
173
174   /**
175    * push a set of new types onto the render order stack. Note - this is a
176    * direct mechanism rather than the one employed in updateRenderOrder
177    * 
178    * @param types
179    */
180   private void pushFeatureType(List<String> types)
181   {
182
183     int ts = types.size();
184     String neworder[] = new String[(renderOrder == null ? 0
185             : renderOrder.length) + ts];
186     types.toArray(neworder);
187     if (renderOrder != null)
188     {
189       System.arraycopy(neworder, 0, neworder, renderOrder.length, ts);
190       System.arraycopy(renderOrder, 0, neworder, 0, renderOrder.length);
191     }
192     renderOrder = neworder;
193   }
194
195   protected Map<String, float[][]> minmax = new Hashtable<String, float[][]>();
196
197   public Map<String, float[][]> getMinMax()
198   {
199     return minmax;
200   }
201
202   /**
203    * normalise a score against the max/min bounds for the feature type.
204    * 
205    * @param sequenceFeature
206    * @return byte[] { signed, normalised signed (-127 to 127) or unsigned
207    *         (0-255) value.
208    */
209   protected final byte[] normaliseScore(SequenceFeature sequenceFeature)
210   {
211     float[] mm = minmax.get(sequenceFeature.type)[0];
212     final byte[] r = new byte[] { 0, (byte) 255 };
213     if (mm != null)
214     {
215       if (r[0] != 0 || mm[0] < 0.0)
216       {
217         r[0] = 1;
218         r[1] = (byte) ((int) 128.0 + 127.0 * (sequenceFeature.score / mm[1]));
219       }
220       else
221       {
222         r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1]));
223       }
224     }
225     return r;
226   }
227
228   boolean newFeatureAdded = false;
229
230   boolean findingFeatures = false;
231
232   protected boolean updateFeatures()
233   {
234     if (av.getFeaturesDisplayed() == null || renderOrder == null
235             || newFeatureAdded)
236     {
237       findAllFeatures();
238       if (av.getFeaturesDisplayed().getVisibleFeatureCount() < 1)
239       {
240         return false;
241       }
242     }
243     // TODO: decide if we should check for the visible feature count first
244     return true;
245   }
246
247   /**
248    * search the alignment for all new features, give them a colour and display
249    * them. Then fires a PropertyChangeEvent on the changeSupport object.
250    * 
251    */
252   protected void findAllFeatures()
253   {
254     synchronized (firing)
255     {
256       if (firing.equals(Boolean.FALSE))
257       {
258         firing = Boolean.TRUE;
259         findAllFeatures(true); // add all new features as visible
260         changeSupport.firePropertyChange("changeSupport", null, null);
261         firing = Boolean.FALSE;
262       }
263     }
264   }
265
266   @Override
267   public List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
268   {
269     ArrayList<SequenceFeature> tmp = new ArrayList<SequenceFeature>();
270     SequenceFeature[] features = sequence.getSequenceFeatures();
271
272     if (features != null)
273     {
274       for (int i = 0; i < features.length; i++)
275       {
276         if (!av.areFeaturesDisplayed()
277                 || !av.getFeaturesDisplayed().isVisible(
278                         features[i].getType()))
279         {
280           continue;
281         }
282
283         if (features[i].featureGroup != null
284                 && featureGroups != null
285                 && featureGroups.containsKey(features[i].featureGroup)
286                 && !featureGroups.get(features[i].featureGroup)
287                         .booleanValue())
288         {
289           continue;
290         }
291
292         if ((features[i].getBegin() <= res)
293                 && (features[i].getEnd() >= res))
294         {
295           tmp.add(features[i]);
296         }
297       }
298     }
299     return tmp;
300   }
301
302   /**
303    * Searches alignment for all features and updates colours
304    * 
305    * @param newMadeVisible
306    *          if true newly added feature types will be rendered immediatly
307    *          TODO: check to see if this method should actually be proxied so
308    *          repaint events can be propagated by the renderer code
309    */
310   @Override
311   public synchronized void findAllFeatures(boolean newMadeVisible)
312   {
313     newFeatureAdded = false;
314
315     if (findingFeatures)
316     {
317       newFeatureAdded = true;
318       return;
319     }
320
321     findingFeatures = true;
322     if (av.getFeaturesDisplayed() == null)
323     {
324       av.setFeaturesDisplayed(new FeaturesDisplayed());
325     }
326     FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed();
327
328     ArrayList<String> allfeatures = new ArrayList<String>();
329     ArrayList<String> oldfeatures = new ArrayList<String>();
330     if (renderOrder != null)
331     {
332       for (int i = 0; i < renderOrder.length; i++)
333       {
334         if (renderOrder[i] != null)
335         {
336           oldfeatures.add(renderOrder[i]);
337         }
338       }
339     }
340     if (minmax == null)
341     {
342       minmax = new Hashtable<String, float[][]>();
343     }
344     AlignmentI alignment = av.getAlignment();
345     for (int i = 0; i < alignment.getHeight(); i++)
346     {
347       SequenceI asq = alignment.getSequenceAt(i);
348       SequenceFeature[] features = asq.getSequenceFeatures();
349
350       if (features == null)
351       {
352         continue;
353       }
354
355       int index = 0;
356       while (index < features.length)
357       {
358         if (!featuresDisplayed.isRegistered(features[index].getType()))
359         {
360           String fgrp = features[index].getFeatureGroup();
361           if (fgrp != null)
362           {
363             Boolean groupDisplayed = featureGroups.get(fgrp);
364             if (groupDisplayed == null)
365             {
366               groupDisplayed = Boolean.valueOf(newMadeVisible);
367               featureGroups.put(fgrp, groupDisplayed);
368             }
369             if (!groupDisplayed.booleanValue())
370             {
371               index++;
372               continue;
373             }
374           }
375           if (!(features[index].begin == 0 && features[index].end == 0))
376           {
377             // If beginning and end are 0, the feature is for the whole sequence
378             // and we don't want to render the feature in the normal way
379
380             if (newMadeVisible
381                     && !oldfeatures.contains(features[index].getType()))
382             {
383               // this is a new feature type on the alignment. Mark it for
384               // display.
385               featuresDisplayed.setVisible(features[index].getType());
386               setOrder(features[index].getType(), 0);
387             }
388           }
389         }
390         if (!allfeatures.contains(features[index].getType()))
391         {
392           allfeatures.add(features[index].getType());
393         }
394         if (!Float.isNaN(features[index].score))
395         {
396           int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
397           float[][] mm = minmax.get(features[index].getType());
398           if (mm == null)
399           {
400             mm = new float[][] { null, null };
401             minmax.put(features[index].getType(), mm);
402           }
403           if (mm[nonpos] == null)
404           {
405             mm[nonpos] = new float[] { features[index].score,
406                 features[index].score };
407
408           }
409           else
410           {
411             if (mm[nonpos][0] > features[index].score)
412             {
413               mm[nonpos][0] = features[index].score;
414             }
415             if (mm[nonpos][1] < features[index].score)
416             {
417               mm[nonpos][1] = features[index].score;
418             }
419           }
420         }
421         index++;
422       }
423     }
424     updateRenderOrder(allfeatures);
425     findingFeatures = false;
426   }
427
428   protected Boolean firing = Boolean.FALSE;
429
430   /**
431    * replaces the current renderOrder with the unordered features in
432    * allfeatures. The ordering of any types in both renderOrder and allfeatures
433    * is preserved, and all new feature types are rendered on top of the existing
434    * types, in the order given by getOrder or the order given in allFeatures.
435    * Note. this operates directly on the featureOrder hash for efficiency. TODO:
436    * eliminate the float storage for computing/recalling the persistent ordering
437    * New Cability: updates min/max for colourscheme range if its dynamic
438    * 
439    * @param allFeatures
440    */
441   private void updateRenderOrder(List<String> allFeatures)
442   {
443     List<String> allfeatures = new ArrayList<String>(allFeatures);
444     String[] oldRender = renderOrder;
445     renderOrder = new String[allfeatures.size()];
446     boolean initOrders = (featureOrder == null);
447     int opos = 0;
448     if (oldRender != null && oldRender.length > 0)
449     {
450       for (int j = 0; j < oldRender.length; j++)
451       {
452         if (oldRender[j] != null)
453         {
454           if (initOrders)
455           {
456             setOrder(oldRender[j], (1 - (1 + (float) j) / oldRender.length));
457           }
458           if (allfeatures.contains(oldRender[j]))
459           {
460             renderOrder[opos++] = oldRender[j]; // existing features always
461             // appear below new features
462             allfeatures.remove(oldRender[j]);
463             if (minmax != null)
464             {
465               float[][] mmrange = minmax.get(oldRender[j]);
466               if (mmrange != null)
467               {
468                 FeatureColourI fc = featureColours.get(oldRender[j]);
469                 if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled())
470                 {
471                   fc.updateBounds(mmrange[0][0], mmrange[0][1]);
472                 }
473               }
474             }
475           }
476         }
477       }
478     }
479     if (allfeatures.size() == 0)
480     {
481       // no new features - leave order unchanged.
482       return;
483     }
484     int i = allfeatures.size() - 1;
485     int iSize = i;
486     boolean sort = false;
487     String[] newf = new String[allfeatures.size()];
488     float[] sortOrder = new float[allfeatures.size()];
489     for (String newfeat : allfeatures)
490     {
491       newf[i] = newfeat;
492       if (minmax != null)
493       {
494         // update from new features minmax if necessary
495         float[][] mmrange = minmax.get(newf[i]);
496         if (mmrange != null)
497         {
498           FeatureColourI fc = featureColours.get(newf[i]);
499           if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled())
500           {
501             fc.updateBounds(mmrange[0][0], mmrange[0][1]);
502           }
503         }
504       }
505       if (initOrders || !featureOrder.containsKey(newf[i]))
506       {
507         int denom = initOrders ? allfeatures.size() : featureOrder.size();
508         // new unordered feature - compute persistent ordering at head of
509         // existing features.
510         setOrder(newf[i], i / (float) denom);
511       }
512       // set order from newly found feature from persisted ordering.
513       sortOrder[i] = 2 - featureOrder.get(newf[i]).floatValue();
514       if (i < iSize)
515       {
516         // only sort if we need to
517         sort = sort || sortOrder[i] > sortOrder[i + 1];
518       }
519       i--;
520     }
521     if (iSize > 1 && sort)
522     {
523       jalview.util.QuickSort.sort(sortOrder, newf);
524     }
525     sortOrder = null;
526     System.arraycopy(newf, 0, renderOrder, opos, newf.length);
527   }
528
529   /**
530    * get a feature style object for the given type string. Creates a
531    * java.awt.Color for a featureType with no existing colourscheme.
532    * 
533    * @param featureType
534    * @return
535    */
536   @Override
537   public FeatureColourI getFeatureStyle(String featureType)
538   {
539     FeatureColourI fc = featureColours.get(featureType);
540     if (fc == null)
541     {
542       Color col = UserColourScheme.createColourFromName(featureType);
543       fc = new FeatureColour(col);
544       featureColours.put(featureType, fc);
545     }
546     return fc;
547   }
548
549   /**
550    * calculate the render colour for a specific feature using current feature
551    * settings.
552    * 
553    * @param feature
554    * @return render colour for the given feature
555    */
556   public Color getColour(SequenceFeature feature)
557   {
558     FeatureColourI fc = getFeatureStyle(feature.getType());
559     return fc.getColor(feature);
560   }
561
562   protected boolean showFeature(SequenceFeature sequenceFeature)
563   {
564     FeatureColourI fc = getFeatureStyle(sequenceFeature.type);
565     return fc.isColored(sequenceFeature);
566   }
567
568   protected boolean showFeatureOfType(String type)
569   {
570     return av.getFeaturesDisplayed().isVisible(type);
571   }
572
573   @Override
574   public void setColour(String featureType, FeatureColourI col)
575   {
576      featureColours.put(featureType, col);
577   }
578
579   public void setTransparency(float value)
580   {
581     transparency = value;
582   }
583
584   public float getTransparency()
585   {
586     return transparency;
587   }
588
589   /**
590    * analogous to colour - store a normalized ordering for all feature types in
591    * this rendering context.
592    * 
593    * @param type
594    *          Feature type string
595    * @param position
596    *          normalized priority - 0 means always appears on top, 1 means
597    *          always last.
598    */
599   public float setOrder(String type, float position)
600   {
601     if (featureOrder == null)
602     {
603       featureOrder = new Hashtable<String, Float>();
604     }
605     featureOrder.put(type, new Float(position));
606     return position;
607   }
608
609   /**
610    * get the global priority (0 (top) to 1 (bottom))
611    * 
612    * @param type
613    * @return [0,1] or -1 for a type without a priority
614    */
615   public float getOrder(String type)
616   {
617     if (featureOrder != null)
618     {
619       if (featureOrder.containsKey(type))
620       {
621         return featureOrder.get(type).floatValue();
622       }
623     }
624     return -1;
625   }
626
627   @Override
628   public Map<String, FeatureColourI> getFeatureColours()
629   {
630     return featureColours;
631   }
632
633   /**
634    * Replace current ordering with new ordering
635    * 
636    * @param data
637    *          { String(Type), Colour(Type), Boolean(Displayed) }
638    * @return true if any visible features have been reordered, else false
639    */
640   public boolean setFeaturePriority(Object[][] data)
641   {
642     return setFeaturePriority(data, true);
643   }
644
645   /**
646    * Sets the priority order for features
647    * 
648    * @param data
649    *          { String(Type), Colour(Type), Boolean(Displayed) }
650    * @param visibleNew
651    *          when true current featureDisplay list will be cleared
652    * @return true if any visible features have been reordered or recoloured,
653    *         else false (i.e. no need to repaint)
654    */
655   public boolean setFeaturePriority(Object[][] data, boolean visibleNew)
656   {
657     /*
658      * note visible feature ordering and colours before update
659      */
660     List<String> visibleFeatures = getDisplayedFeatureTypes();
661     Map<String, FeatureColourI> visibleColours = new HashMap<String, FeatureColourI>(
662             getFeatureColours());
663
664     FeaturesDisplayedI av_featuresdisplayed = null;
665     if (visibleNew)
666     {
667       if ((av_featuresdisplayed = av.getFeaturesDisplayed()) != null)
668       {
669         av.getFeaturesDisplayed().clear();
670       }
671       else
672       {
673         av.setFeaturesDisplayed(av_featuresdisplayed = new FeaturesDisplayed());
674       }
675     }
676     else
677     {
678       av_featuresdisplayed = av.getFeaturesDisplayed();
679     }
680     if (data == null)
681     {
682       return false;
683     }
684     // The feature table will display high priority
685     // features at the top, but these are the ones
686     // we need to render last, so invert the data
687     renderOrder = new String[data.length];
688
689     if (data.length > 0)
690     {
691       for (int i = 0; i < data.length; i++)
692       {
693         String type = data[i][0].toString();
694         setColour(type, (FeatureColourI) data[i][1]);
695         if (((Boolean) data[i][2]).booleanValue())
696         {
697           av_featuresdisplayed.setVisible(type);
698         }
699
700         renderOrder[data.length - i - 1] = type;
701       }
702     }
703
704     /*
705      * get the new visible ordering and return true if it has changed
706      * order or any colour has changed
707      */
708     List<String> reorderedVisibleFeatures = getDisplayedFeatureTypes();
709     if (!visibleFeatures.equals(reorderedVisibleFeatures))
710     {
711       /*
712        * the list of ordered visible features has changed
713        */
714       return true;
715     }
716
717     /*
718      * return true if any feature colour has changed
719      */
720     for (String feature : visibleFeatures)
721     {
722       if (visibleColours.get(feature) != getFeatureStyle(feature))
723       {
724         return true;
725       }
726     }
727     return false;
728   }
729
730   /**
731    * @param listener
732    * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
733    */
734   public void addPropertyChangeListener(PropertyChangeListener listener)
735   {
736     changeSupport.addPropertyChangeListener(listener);
737   }
738
739   /**
740    * @param listener
741    * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
742    */
743   public void removePropertyChangeListener(PropertyChangeListener listener)
744   {
745     changeSupport.removePropertyChangeListener(listener);
746   }
747
748   public Set<String> getAllFeatureColours()
749   {
750     return featureColours.keySet();
751   }
752
753   public void clearRenderOrder()
754   {
755     renderOrder = null;
756   }
757
758   public boolean hasRenderOrder()
759   {
760     return renderOrder != null;
761   }
762
763   /**
764    * Returns feature types in ordering of rendering, where last means on top
765    */
766   public List<String> getRenderOrder()
767   {
768     if (renderOrder == null)
769     {
770       return Arrays.asList(new String[] {});
771     }
772     return Arrays.asList(renderOrder);
773   }
774
775   public int getFeatureGroupsSize()
776   {
777     return featureGroups != null ? 0 : featureGroups.size();
778   }
779
780   @Override
781   public List<String> getFeatureGroups()
782   {
783     // conflict between applet and desktop - featureGroups returns the map in
784     // the desktop featureRenderer
785     return (featureGroups == null) ? Arrays.asList(new String[0]) : Arrays
786             .asList(featureGroups.keySet().toArray(new String[0]));
787   }
788
789   public boolean checkGroupVisibility(String group, boolean newGroupsVisible)
790   {
791     if (featureGroups == null)
792     {
793       // then an exception happens next..
794     }
795     if (featureGroups.containsKey(group))
796     {
797       return featureGroups.get(group).booleanValue();
798     }
799     if (newGroupsVisible)
800     {
801       featureGroups.put(group, new Boolean(true));
802       return true;
803     }
804     return false;
805   }
806
807   /**
808    * get visible or invisible groups
809    * 
810    * @param visible
811    *          true to return visible groups, false to return hidden ones.
812    * @return list of groups
813    */
814   @Override
815   public List getGroups(boolean visible)
816   {
817     if (featureGroups != null)
818     {
819       List<String> gp = new ArrayList<String>();
820
821       for (String grp : featureGroups.keySet())
822       {
823         Boolean state = featureGroups.get(grp);
824         if (state.booleanValue() == visible)
825         {
826           gp.add(grp);
827         }
828       }
829       return gp;
830     }
831     return null;
832   }
833
834   @Override
835   public void setGroupVisibility(String group, boolean visible)
836   {
837     featureGroups.put(group, new Boolean(visible));
838   }
839
840   @Override
841   public void setGroupVisibility(List<String> toset, boolean visible)
842   {
843     if (toset != null && toset.size() > 0 && featureGroups != null)
844     {
845       boolean rdrw = false;
846       for (String gst : toset)
847       {
848         Boolean st = featureGroups.get(gst);
849         featureGroups.put(gst, new Boolean(visible));
850         if (st != null)
851         {
852           rdrw = rdrw || (visible != st.booleanValue());
853         }
854       }
855       if (rdrw)
856       {
857         // set local flag indicating redraw needed ?
858       }
859     }
860   }
861
862   @Override
863   public Map<String, FeatureColourI> getDisplayedFeatureCols()
864   {
865     Map<String, FeatureColourI> fcols = new Hashtable<String, FeatureColourI>();
866     if (getViewport().getFeaturesDisplayed() == null)
867     {
868       return fcols;
869     }
870     Iterator<String> features = getViewport().getFeaturesDisplayed()
871             .getVisibleFeatures();
872     while (features.hasNext())
873     {
874       String feature = features.next();
875       fcols.put(feature, getFeatureStyle(feature));
876     }
877     return fcols;
878   }
879
880   @Override
881   public FeaturesDisplayedI getFeaturesDisplayed()
882   {
883     return av.getFeaturesDisplayed();
884   }
885
886   /**
887    * Returns a (possibly empty) list of visible feature types, in render order
888    * (last is on top)
889    */
890   @Override
891   public List<String> getDisplayedFeatureTypes()
892   {
893     List<String> typ = getRenderOrder();
894     List<String> displayed = new ArrayList<String>();
895     FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed();
896     if (feature_disp != null)
897     {
898       synchronized (feature_disp)
899       {
900         for (String type : typ)
901         {
902           if (feature_disp.isVisible(type))
903           {
904             displayed.add(type);
905           }
906         }
907       }
908     }
909     return displayed;
910   }
911
912   @Override
913   public List<String> getDisplayedFeatureGroups()
914   {
915     List<String> _gps = new ArrayList<String>();
916     boolean valid = false;
917     for (String gp : getFeatureGroups())
918     {
919       if (checkGroupVisibility(gp, false))
920       {
921         valid = true;
922         _gps.add(gp);
923       }
924       if (!valid)
925       {
926         return null;
927       }
928       else
929       {
930         // gps = new String[_gps.size()];
931         // _gps.toArray(gps);
932       }
933     }
934     return _gps;
935   }
936
937 }