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