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