bcf808df4638265c09f3e9243232231dbce284df
[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       SequenceI dasq = asq.getDatasetSequence();
333       SequenceFeature[] features = dasq != null ? dasq
334               .getSequenceFeatures() : asq.getSequenceFeatures();
335
336       if (features == null)
337       {
338         continue;
339       }
340
341       int index = 0;
342       while (index < features.length)
343       {
344         if (!featuresDisplayed.isRegistered(features[index].getType()))
345         {
346           String fgrp = features[index].getFeatureGroup();
347           if (fgrp != null)
348           {
349             Boolean groupDisplayed = featureGroups.get(fgrp);
350             if (groupDisplayed == null)
351             {
352               groupDisplayed = Boolean.valueOf(newMadeVisible);
353               featureGroups.put(fgrp, groupDisplayed);
354             }
355             if (!groupDisplayed.booleanValue())
356             {
357               index++;
358               continue;
359             }
360           }
361           if (!(features[index].begin == 0 && features[index].end == 0))
362           {
363             // If beginning and end are 0, the feature is for the whole sequence
364             // and we don't want to render the feature in the normal way
365
366             if (newMadeVisible
367                     && !oldfeatures.contains(features[index].getType()))
368             {
369               // this is a new feature type on the alignment. Mark it for
370               // display.
371               featuresDisplayed.setVisible(features[index].getType());
372               setOrder(features[index].getType(), 0);
373             }
374           }
375         }
376         if (!allfeatures.contains(features[index].getType()))
377         {
378           allfeatures.add(features[index].getType());
379         }
380         if (features[index].score != Float.NaN)
381         {
382           int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
383           float[][] mm = (float[][]) minmax.get(features[index].getType());
384           if (mm == null)
385           {
386             mm = new float[][]
387             { null, null };
388             minmax.put(features[index].getType(), mm);
389           }
390           if (mm[nonpos] == null)
391           {
392             mm[nonpos] = new float[]
393             { features[index].score, features[index].score };
394
395           }
396           else
397           {
398             if (mm[nonpos][0] > features[index].score)
399             {
400               mm[nonpos][0] = features[index].score;
401             }
402             if (mm[nonpos][1] < features[index].score)
403             {
404               mm[nonpos][1] = features[index].score;
405             }
406           }
407         }
408         index++;
409       }
410     }
411     updateRenderOrder(allfeatures);
412     findingFeatures = false;
413   }
414
415   protected Boolean firing = Boolean.FALSE;
416
417   /**
418    * replaces the current renderOrder with the unordered features in
419    * allfeatures. The ordering of any types in both renderOrder and allfeatures
420    * is preserved, and all new feature types are rendered on top of the existing
421    * types, in the order given by getOrder or the order given in allFeatures.
422    * Note. this operates directly on the featureOrder hash for efficiency. TODO:
423    * eliminate the float storage for computing/recalling the persistent ordering
424    * New Cability: updates min/max for colourscheme range if its dynamic
425    * 
426    * @param allFeatures
427    */
428   private void updateRenderOrder(List<String> allFeatures)
429   {
430     List<String> allfeatures = new ArrayList<String>(allFeatures);
431     String[] oldRender = renderOrder;
432     renderOrder = new String[allfeatures.size()];
433     Object mmrange, fc = null;
434     boolean initOrders = (featureOrder == null);
435     int opos = 0;
436     if (oldRender != null && oldRender.length > 0)
437     {
438       for (int j = 0; j < oldRender.length; j++)
439       {
440         if (oldRender[j] != null)
441         {
442           if (initOrders)
443           {
444             setOrder(oldRender[j], (1 - (1 + (float) j)
445                     / oldRender.length));
446           }
447           if (allfeatures.contains(oldRender[j]))
448           {
449             renderOrder[opos++] = oldRender[j]; // existing features always
450             // appear below new features
451             allfeatures.remove(oldRender[j]);
452             if (minmax != null)
453             {
454               mmrange = minmax.get(oldRender[j]);
455               if (mmrange != null)
456               {
457                 fc = featureColours.get(oldRender[j]);
458                 if (fc != null && fc instanceof GraduatedColor
459                         && ((GraduatedColor) fc).isAutoScale())
460                 {
461                   ((GraduatedColor) fc).updateBounds(
462                           ((float[][]) mmrange)[0][0],
463                           ((float[][]) mmrange)[0][1]);
464                 }
465               }
466             }
467           }
468         }
469       }
470     }
471     if (allfeatures.size() == 0)
472     {
473       // no new features - leave order unchanged.
474       return;
475     }
476     int i = allfeatures.size() - 1;
477     int iSize = i;
478     boolean sort = false;
479     String[] newf = new String[allfeatures.size()];
480     float[] sortOrder = new float[allfeatures.size()];
481     for (String newfeat : allfeatures)
482     {
483       newf[i] = newfeat;
484       if (minmax != null)
485       {
486         // update from new features minmax if necessary
487         mmrange = minmax.get(newf[i]);
488         if (mmrange != null)
489         {
490           fc = featureColours.get(newf[i]);
491           if (fc != null && fc instanceof GraduatedColor
492                   && ((GraduatedColor) fc).isAutoScale())
493           {
494             ((GraduatedColor) fc).updateBounds(((float[][]) mmrange)[0][0],
495                     ((float[][]) mmrange)[0][1]);
496           }
497         }
498       }
499       if (initOrders || !featureOrder.containsKey(newf[i]))
500       {
501         int denom = initOrders ? allfeatures.size() : featureOrder.size();
502         // new unordered feature - compute persistent ordering at head of
503         // existing features.
504         setOrder(newf[i], i / (float) denom);
505       }
506       // set order from newly found feature from persisted ordering.
507       sortOrder[i] = 2 - ((Float) featureOrder.get(newf[i])).floatValue();
508       if (i < iSize)
509       {
510         // only sort if we need to
511         sort = sort || sortOrder[i] > sortOrder[i + 1];
512       }
513       i--;
514     }
515     if (iSize > 1 && sort)
516     {
517       jalview.util.QuickSort.sort(sortOrder, newf);
518     }
519     sortOrder = null;
520     System.arraycopy(newf, 0, renderOrder, opos, newf.length);
521   }
522
523   /**
524    * get a feature style object for the given type string. Creates a
525    * java.awt.Color for a featureType with no existing colourscheme. TODO:
526    * replace return type with object implementing standard abstract colour/style
527    * interface
528    * 
529    * @param featureType
530    * @return java.awt.Color or GraduatedColor
531    */
532   public Object getFeatureStyle(String featureType)
533   {
534     Object fc = featureColours.get(featureType);
535     if (fc == null)
536     {
537       jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
538       Color col = ucs.createColourFromName(featureType);
539       featureColours.put(featureType, fc = col);
540     }
541     return fc;
542   }
543
544   /**
545    * return a nominal colour for this feature
546    * 
547    * @param featureType
548    * @return standard color, or maximum colour for graduated colourscheme
549    */
550   public Color getColour(String featureType)
551   {
552     Object fc = getFeatureStyle(featureType);
553
554     if (fc instanceof Color)
555     {
556       return (Color) fc;
557     }
558     else
559     {
560       if (fc instanceof GraduatedColor)
561       {
562         return ((GraduatedColor) fc).getMaxColor();
563       }
564     }
565     throw new Error("Implementation Error: Unrecognised render object "
566             + fc.getClass() + " for features of type " + featureType);
567   }
568
569   /**
570    * calculate the render colour for a specific feature using current feature
571    * settings.
572    * 
573    * @param feature
574    * @return render colour for the given feature
575    */
576   public Color getColour(SequenceFeature feature)
577   {
578     Object fc = getFeatureStyle(feature.getType());
579     if (fc instanceof Color)
580     {
581       return (Color) fc;
582     }
583     else
584     {
585       if (fc instanceof GraduatedColor)
586       {
587         return ((GraduatedColor) fc).findColor(feature);
588       }
589     }
590     throw new Error("Implementation Error: Unrecognised render object "
591             + fc.getClass() + " for features of type " + feature.getType());
592   }
593
594   protected boolean showFeature(SequenceFeature sequenceFeature)
595   {
596     Object fc = getFeatureStyle(sequenceFeature.type);
597     if (fc instanceof GraduatedColor)
598     {
599       return ((GraduatedColor) fc).isColored(sequenceFeature);
600     }
601     else
602     {
603       return true;
604     }
605   }
606
607   protected boolean showFeatureOfType(String type)
608   {
609     return av.getFeaturesDisplayed().isVisible(type);
610   }
611
612   public void setColour(String featureType, Object col)
613   {
614     // overwrite
615     // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
616     // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
617     // Object c = featureColours.get(featureType);
618     // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
619     // !((GraduatedColor)c).getMaxColor().equals(_col)))
620     {
621       featureColours.put(featureType, col);
622     }
623   }
624
625   public void setTransparency(float value)
626   {
627     transparency = value;
628   }
629
630   public float getTransparency()
631   {
632     return transparency;
633   }
634
635   Map featureOrder = null;
636
637   /**
638    * analogous to colour - store a normalized ordering for all feature types in
639    * this rendering context.
640    * 
641    * @param type
642    *          Feature type string
643    * @param position
644    *          normalized priority - 0 means always appears on top, 1 means
645    *          always last.
646    */
647   public float setOrder(String type, float position)
648   {
649     if (featureOrder == null)
650     {
651       featureOrder = new Hashtable();
652     }
653     featureOrder.put(type, new Float(position));
654     return position;
655   }
656
657   /**
658    * get the global priority (0 (top) to 1 (bottom))
659    * 
660    * @param type
661    * @return [0,1] or -1 for a type without a priority
662    */
663   public float getOrder(String type)
664   {
665     if (featureOrder != null)
666     {
667       if (featureOrder.containsKey(type))
668       {
669         return ((Float) featureOrder.get(type)).floatValue();
670       }
671     }
672     return -1;
673   }
674
675   @Override
676   public Map<String, Object> getFeatureColours()
677   {
678     return new ConcurrentHashMap<String, Object>(featureColours);
679   }
680
681   /**
682    * Replace current ordering with new ordering
683    * 
684    * @param data
685    *          { String(Type), Colour(Type), Boolean(Displayed) }
686    */
687   public void setFeaturePriority(Object[][] data)
688   {
689     setFeaturePriority(data, true);
690   }
691
692   /**
693    * 
694    * @param data
695    *          { String(Type), Colour(Type), Boolean(Displayed) }
696    * @param visibleNew
697    *          when true current featureDisplay list will be cleared
698    */
699   public void setFeaturePriority(Object[][] data, boolean visibleNew)
700   {
701     FeaturesDisplayedI av_featuresdisplayed = null;
702     if (visibleNew)
703     {
704       if ((av_featuresdisplayed = av.getFeaturesDisplayed()) != null)
705       {
706         av.getFeaturesDisplayed().clear();
707       }
708       else
709       {
710         av.setFeaturesDisplayed(av_featuresdisplayed = new FeaturesDisplayed());
711       }
712     }
713     else
714     {
715       av_featuresdisplayed = av.getFeaturesDisplayed();
716     }
717     if (data == null)
718     {
719       return;
720     }
721     // The feature table will display high priority
722     // features at the top, but theses are the ones
723     // we need to render last, so invert the data
724     renderOrder = new String[data.length];
725
726     if (data.length > 0)
727     {
728       for (int i = 0; i < data.length; i++)
729       {
730         String type = data[i][0].toString();
731         setColour(type, data[i][1]); // todo : typesafety - feature color
732         // interface object
733         if (((Boolean) data[i][2]).booleanValue())
734         {
735           av_featuresdisplayed.setVisible(type);
736         }
737
738         renderOrder[data.length - i - 1] = type;
739       }
740     }
741
742   }
743
744   /**
745    * @param listener
746    * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
747    */
748   public void addPropertyChangeListener(PropertyChangeListener listener)
749   {
750     changeSupport.addPropertyChangeListener(listener);
751   }
752
753   /**
754    * @param listener
755    * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
756    */
757   public void removePropertyChangeListener(PropertyChangeListener listener)
758   {
759     changeSupport.removePropertyChangeListener(listener);
760   }
761
762   public Set getAllFeatureColours()
763   {
764     return featureColours.keySet();
765   }
766
767   public void clearRenderOrder()
768   {
769     renderOrder = null;
770   }
771
772   public boolean hasRenderOrder()
773   {
774     return renderOrder != null;
775   }
776
777   public List<String> getRenderOrder()
778   {
779     if (renderOrder == null)
780     {
781       return Arrays.asList(new String[]
782       {});
783     }
784     return Arrays.asList(renderOrder);
785   }
786
787   public int getFeatureGroupsSize()
788   {
789     return featureGroups != null ? 0 : featureGroups.size();
790   }
791
792   @Override
793   public List<String> getFeatureGroups()
794   {
795     // conflict between applet and desktop - featureGroups returns the map in
796     // the desktop featureRenderer
797     return (featureGroups == null) ? Arrays.asList(new String[0]) : Arrays
798             .asList(featureGroups.keySet().toArray(new String[0]));
799   }
800
801   public boolean checkGroupVisibility(String group, boolean newGroupsVisible)
802   {
803     if (featureGroups == null)
804     {
805       // then an exception happens next..
806     }
807     if (featureGroups.containsKey(group))
808     {
809       return featureGroups.get(group).booleanValue();
810     }
811     if (newGroupsVisible)
812     {
813       featureGroups.put(group, new Boolean(true));
814       return true;
815     }
816     return false;
817   }
818
819   /**
820    * get visible or invisible groups
821    * 
822    * @param visible
823    *          true to return visible groups, false to return hidden ones.
824    * @return list of groups
825    */
826   @Override
827   public List getGroups(boolean visible)
828   {
829     if (featureGroups != null)
830     {
831       ArrayList gp = new ArrayList();
832
833       for (Object grp : featureGroups.keySet())
834       {
835         Boolean state = featureGroups.get(grp);
836         if (state.booleanValue() == visible)
837         {
838           gp.add(grp);
839         }
840       }
841       return gp;
842     }
843     return null;
844   }
845
846   @Override
847   public void setGroupVisibility(String group, boolean visible)
848   {
849     featureGroups.put(group, new Boolean(visible));
850   }
851
852   @Override
853   public void setGroupVisibility(List<String> toset, boolean visible)
854   {
855     if (toset != null && toset.size() > 0 && featureGroups != null)
856     {
857       boolean rdrw = false;
858       for (String gst : toset)
859       {
860         Boolean st = featureGroups.get(gst);
861         featureGroups.put(gst, new Boolean(visible));
862         if (st != null)
863         {
864           rdrw = rdrw || (visible != st.booleanValue());
865         }
866       }
867       if (rdrw)
868       {
869         // set local flag indicating redraw needed ?
870       }
871     }
872   }
873
874   @Override
875   public Hashtable getDisplayedFeatureCols()
876   {
877     Hashtable fcols = new Hashtable();
878     if (getViewport().getFeaturesDisplayed() == null)
879     {
880       return fcols;
881     }
882     Iterator<String> en = getViewport().getFeaturesDisplayed()
883             .getVisibleFeatures();
884     while (en.hasNext())
885     {
886       String col = en.next();
887       fcols.put(col, getColour(col));
888     }
889     return fcols;
890   }
891
892   @Override
893   public FeaturesDisplayedI getFeaturesDisplayed()
894   {
895     return av.getFeaturesDisplayed();
896   }
897
898   @Override
899   public String[] getDisplayedFeatureTypes()
900   {
901     String[] typ = null;
902     typ = getRenderOrder().toArray(new String[0]);
903     FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed();
904     if (feature_disp != null)
905     {
906       synchronized (feature_disp)
907       {
908         for (int i = 0; i < typ.length; i++)
909         {
910           if (feature_disp.isVisible(typ[i]))
911           {
912             typ[i] = null;
913           }
914         }
915       }
916     }
917     return typ;
918   }
919
920   @Override
921   public String[] getDisplayedFeatureGroups()
922   {
923     String[] gps = null;
924     ArrayList<String> _gps = new ArrayList<String>();
925     Iterator en = getFeatureGroups().iterator();
926     int g = 0;
927     boolean valid = false;
928     while (en.hasNext())
929     {
930       String gp = (String) en.next();
931       if (checkGroupVisibility(gp, false))
932       {
933         valid = true;
934         _gps.add(gp);
935       }
936       if (!valid)
937       {
938         return null;
939       }
940       else
941       {
942         gps = new String[_gps.size()];
943         _gps.toArray(gps);
944       }
945     }
946     return gps;
947   }
948
949 }