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