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