added feature gradient colours and sortby feature score/density to feature settings...
[jalview.git] / src / jalview / appletgui / FeatureRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.appletgui;
20
21 import java.util.*;
22
23 import java.awt.*;
24
25 import java.awt.event.*;
26
27 import jalview.appletgui.FeatureSettings.MyCheckbox;
28 import jalview.datamodel.*;
29 import jalview.schemes.GraduatedColor;
30
31 /**
32  * DOCUMENT ME!
33  * 
34  * @author $author$
35  * @version $Revision$
36  */
37 public class FeatureRenderer
38 {
39   AlignViewport av;
40
41   Hashtable featureColours = new Hashtable();
42
43   // A higher level for grouping features of a
44   // particular type
45   Hashtable featureGroups = null;
46
47   // Holds web links for feature groups and feature types
48   // in the form label|link
49   Hashtable featureLinks = null;
50
51   // This is actually an Integer held in the hashtable,
52   // Retrieved using the key feature type
53   Object currentColour;
54
55   String[] renderOrder;
56
57   FontMetrics fm;
58
59   int charOffset;
60
61   float transparency = 1f;
62
63   TransparencySetter transparencySetter = null;
64
65   /**
66    * Creates a new FeatureRenderer object.
67    * 
68    * @param av
69    *                DOCUMENT ME!
70    */
71   public FeatureRenderer(AlignViewport av)
72   {
73     this.av = av;
74
75     if (!System.getProperty("java.version").startsWith("1.1"))
76     {
77       transparencySetter = new TransparencySetter();
78     }
79   }
80
81   public void transferSettings(FeatureRenderer fr)
82   {
83     renderOrder = fr.renderOrder;
84     featureGroups = fr.featureGroups;
85     featureColours = fr.featureColours;
86     transparency = fr.transparency;
87   }
88
89   static String lastFeatureAdded;
90
91   static String lastFeatureGroupAdded;
92
93   static String lastDescriptionAdded;
94
95   int featureIndex = 0;
96
97   boolean deleteFeature = false;
98
99   Panel colourPanel;
100
101   boolean amendFeatures(final SequenceI[] sequences,
102           final SequenceFeature[] features, boolean newFeatures,
103           final AlignmentPanel ap)
104   {
105     Panel bigPanel = new Panel(new BorderLayout());
106     final TextField name = new TextField(16);
107     final TextField source = new TextField(16);
108     final TextArea description = new TextArea(3, 35);
109     final TextField start = new TextField(8);
110     final TextField end = new TextField(8);
111     final Choice overlaps;
112     Button deleteButton = new Button("Delete");
113     deleteFeature = false;
114
115     colourPanel = new Panel(null);
116     colourPanel.setSize(110, 15);
117     final FeatureRenderer fr = this;
118
119     Panel panel = new Panel(new GridLayout(3, 1));
120
121     Panel tmp;
122
123     // /////////////////////////////////////
124     // /MULTIPLE FEATURES AT SELECTED RESIDUE
125     if (!newFeatures && features.length > 1)
126     {
127       panel = new Panel(new GridLayout(4, 1));
128       tmp = new Panel();
129       tmp.add(new Label("Select Feature: "));
130       overlaps = new Choice();
131       for (int i = 0; i < features.length; i++)
132       {
133         String item = features[i].getType() + "/" + features[i].getBegin()
134                 + "-" + features[i].getEnd();
135
136         if (features[i].getFeatureGroup() != null)
137           item += " (" + features[i].getFeatureGroup() + ")";
138
139         overlaps.addItem(item);
140       }
141
142       tmp.add(overlaps);
143
144       overlaps.addItemListener(new java.awt.event.ItemListener()
145       {
146         public void itemStateChanged(java.awt.event.ItemEvent e)
147         {
148           int index = overlaps.getSelectedIndex();
149           if (index != -1)
150           {
151             featureIndex = index;
152             name.setText(features[index].getType());
153             description.setText(features[index].getDescription());
154             source.setText(features[index].getFeatureGroup());
155             start.setText(features[index].getBegin() + "");
156             end.setText(features[index].getEnd() + "");
157
158             SearchResults highlight = new SearchResults();
159             highlight.addResult(sequences[0], features[index].getBegin(),
160                     features[index].getEnd());
161
162             ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
163
164           }
165           Color col = getColour(name.getText());
166           if (col == null)
167           {
168             col = new jalview.schemes.UserColourScheme()
169                     .createColourFromName(name.getText());
170           }
171
172           colourPanel.setBackground(col);
173         }
174       });
175
176       panel.add(tmp);
177     }
178     // ////////
179     // ////////////////////////////////////
180
181     tmp = new Panel();
182     panel.add(tmp);
183     tmp.add(new Label("Name: ", Label.RIGHT));
184     tmp.add(name);
185
186     tmp = new Panel();
187     panel.add(tmp);
188     tmp.add(new Label("Group: ", Label.RIGHT));
189     tmp.add(source);
190
191     tmp = new Panel();
192     panel.add(tmp);
193     tmp.add(new Label("Colour: ", Label.RIGHT));
194     tmp.add(colourPanel);
195
196     bigPanel.add(panel, BorderLayout.NORTH);
197
198     panel = new Panel();
199     panel.add(new Label("Description: ", Label.RIGHT));
200     panel.add(new ScrollPane().add(description));
201
202     if (!newFeatures)
203     {
204       bigPanel.add(panel, BorderLayout.SOUTH);
205
206       panel = new Panel();
207       panel.add(new Label(" Start:", Label.RIGHT));
208       panel.add(start);
209       panel.add(new Label("  End:", Label.RIGHT));
210       panel.add(end);
211       bigPanel.add(panel, BorderLayout.CENTER);
212     }
213     else
214     {
215       bigPanel.add(panel, BorderLayout.CENTER);
216     }
217
218     if (lastFeatureAdded == null)
219     {
220       if (features[0].type != null)
221       {
222         lastFeatureAdded = features[0].type;
223       }
224       else
225       {
226         lastFeatureAdded = "feature_1";
227       }
228     }
229
230     if (lastFeatureGroupAdded == null)
231     {
232       if (features[0].featureGroup != null)
233       {
234         lastFeatureGroupAdded = features[0].featureGroup;
235       }
236       else
237       {
238         lastFeatureAdded = "Jalview";
239       }
240     }
241
242     String title = newFeatures ? "Create New Sequence Feature(s)"
243             : "Amend/Delete Features for " + sequences[0].getName();
244
245     final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385,
246             240);
247
248     dialog.setMainPanel(bigPanel);
249
250     if (newFeatures)
251     {
252       name.setText(lastFeatureAdded);
253       source.setText(lastFeatureGroupAdded);
254     }
255     else
256     {
257       dialog.ok.setLabel("Amend");
258       dialog.buttonPanel.add(deleteButton, 1);
259       deleteButton.addActionListener(new ActionListener()
260       {
261         public void actionPerformed(ActionEvent evt)
262         {
263           deleteFeature = true;
264           dialog.setVisible(false);
265         }
266       });
267       name.setText(features[0].getType());
268       source.setText(features[0].getFeatureGroup());
269     }
270
271     start.setText(features[0].getBegin() + "");
272     end.setText(features[0].getEnd() + "");
273     description.setText(features[0].getDescription());
274
275     Color col = getColour(name.getText());
276     if (col == null)
277     {
278       col = new jalview.schemes.UserColourScheme()
279               .createColourFromName(name.getText());
280     }
281
282     colourPanel.setBackground(col);
283
284     dialog.setResizable(true);
285
286     colourPanel.addMouseListener(new java.awt.event.MouseAdapter()
287     {
288       public void mousePressed(java.awt.event.MouseEvent evt)
289       {
290         new UserDefinedColours(fr, ap.alignFrame);
291       }
292     });
293
294     dialog.setVisible(true);
295
296     jalview.io.FeaturesFile ffile = new jalview.io.FeaturesFile();
297
298     if (dialog.accept)
299     {
300       // This ensures that the last sequence
301       // is refreshed and new features are rendered
302       lastSeq = null;
303       lastFeatureAdded = name.getText().trim();
304       lastFeatureGroupAdded = source.getText().trim();
305       lastDescriptionAdded = description.getText().replace('\n', ' ');
306     }
307
308     if (lastFeatureGroupAdded != null && lastFeatureGroupAdded.length() < 1)
309       lastFeatureGroupAdded = null;
310
311     if (!newFeatures)
312     {
313       SequenceFeature sf = features[featureIndex];
314
315       if (dialog.accept)
316       {
317         sf.type = lastFeatureAdded;
318         sf.featureGroup = lastFeatureGroupAdded;
319         sf.description = lastDescriptionAdded;
320         setColour(sf.type, colourPanel.getBackground());
321         try
322         {
323           sf.begin = Integer.parseInt(start.getText());
324           sf.end = Integer.parseInt(end.getText());
325         } catch (NumberFormatException ex)
326         {
327         }
328
329         ffile.parseDescriptionHTML(sf, false);
330       }
331       if (deleteFeature)
332       {
333         sequences[0].deleteFeature(sf);
334       }
335
336     }
337     else
338     {
339       if (dialog.accept && name.getText().length() > 0)
340       {
341         for (int i = 0; i < sequences.length; i++)
342         {
343           features[i].type = lastFeatureAdded;
344           features[i].featureGroup = lastFeatureGroupAdded;
345           features[i].description = lastDescriptionAdded;
346           sequences[i].addSequenceFeature(features[i]);
347           ffile.parseDescriptionHTML(features[i], false);
348         }
349
350         if (av.featuresDisplayed == null)
351         {
352           av.featuresDisplayed = new Hashtable();
353         }
354
355         if (featureGroups == null)
356         {
357           featureGroups = new Hashtable();
358         }
359
360         col = colourPanel.getBackground();
361         setColour(lastFeatureAdded, col);
362
363         if (lastFeatureGroupAdded != null)
364         {
365           featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
366           av.featuresDisplayed.put(lastFeatureGroupAdded, new Integer(col
367                   .getRGB()));
368         }
369         findAllFeatures();
370
371         String[] tro = new String[renderOrder.length];
372         tro[0] = renderOrder[renderOrder.length - 1];
373         System.arraycopy(renderOrder, 0, tro, 1, renderOrder.length - 1);
374         renderOrder = tro;
375
376         ap.paintAlignment(true);
377
378         return true;
379       }
380       else
381       {
382         return false;
383       }
384     }
385
386     findAllFeatures();
387
388     ap.paintAlignment(true);
389
390     return true;
391   }
392
393   public Color findFeatureColour(Color initialCol, SequenceI seq, int i)
394   {
395     overview = true;
396     if (!av.showSequenceFeatures)
397     {
398       return initialCol;
399     }
400
401     lastSeq = seq;
402     sequenceFeatures = lastSeq.getSequenceFeatures();
403     if (sequenceFeatures == null)
404     {
405       return initialCol;
406     }
407
408     sfSize = sequenceFeatures.length;
409
410     if (jalview.util.Comparison.isGap(lastSeq.getCharAt(i)))
411     {
412       return Color.white;
413     }
414
415     currentColour = null;
416
417     drawSequence(null, lastSeq, lastSeq.findPosition(i), -1, -1);
418
419     if (currentColour == null)
420     {
421       return initialCol;
422     }
423
424     return new Color(((Integer) currentColour).intValue());
425   }
426
427   /**
428    * This is used by the Molecule Viewer to get the accurate colour of the
429    * rendered sequence
430    */
431   boolean overview = false;
432
433   /**
434    * DOCUMENT ME!
435    * 
436    * @param g
437    *                DOCUMENT ME!
438    * @param seq
439    *                DOCUMENT ME!
440    * @param sg
441    *                DOCUMENT ME!
442    * @param start
443    *                DOCUMENT ME!
444    * @param end
445    *                DOCUMENT ME!
446    * @param x1
447    *                DOCUMENT ME!
448    * @param y1
449    *                DOCUMENT ME!
450    * @param width
451    *                DOCUMENT ME!
452    * @param height
453    *                DOCUMENT ME!
454    */
455   // String type;
456   // SequenceFeature sf;
457   SequenceI lastSeq;
458
459   SequenceFeature[] sequenceFeatures;
460
461   int sfSize, sfindex, spos, epos;
462
463   synchronized public void drawSequence(Graphics g, SequenceI seq,
464           int start, int end, int y1)
465   {
466     if (seq.getSequenceFeatures() == null
467             || seq.getSequenceFeatures().length == 0)
468     {
469       return;
470     }
471
472     if (transparencySetter != null && g != null)
473     {
474       transparencySetter.setTransparency(g, transparency);
475     }
476
477     if (lastSeq == null || seq != lastSeq
478             || sequenceFeatures != seq.getSequenceFeatures())
479     {
480       lastSeq = seq;
481       sequenceFeatures = seq.getSequenceFeatures();
482       sfSize = sequenceFeatures.length;
483     }
484
485     if (av.featuresDisplayed == null || renderOrder == null)
486     {
487       findAllFeatures();
488       if (av.featuresDisplayed.size() < 1)
489       {
490         return;
491       }
492
493       sequenceFeatures = seq.getSequenceFeatures();
494       sfSize = sequenceFeatures.length;
495     }
496     if (!overview)
497     {
498       spos = lastSeq.findPosition(start);
499       epos = lastSeq.findPosition(end);
500       if (g != null)
501       {
502         fm = g.getFontMetrics();
503       }
504     }
505     String type;
506     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
507     {
508       type = renderOrder[renderIndex];
509       if (!av.featuresDisplayed.containsKey(type))
510       {
511         continue;
512       }
513
514       // loop through all features in sequence to find
515       // current feature to render
516       for (sfindex = 0; sfindex < sfSize; sfindex++)
517       {
518         if (!sequenceFeatures[sfindex].type.equals(type))
519         {
520           continue;
521         }
522
523         if (featureGroups != null
524                 && sequenceFeatures[sfindex].featureGroup != null
525                 && featureGroups
526                         .containsKey(sequenceFeatures[sfindex].featureGroup)
527                 && !((Boolean) featureGroups
528                         .get(sequenceFeatures[sfindex].featureGroup))
529                         .booleanValue())
530         {
531           continue;
532         }
533
534         if (!overview
535                 && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
536                         .getEnd() < spos))
537         {
538           continue;
539         }
540
541         if (overview)
542         {
543           if (sequenceFeatures[sfindex].begin <= start
544                   && sequenceFeatures[sfindex].end >= start)
545           {
546             currentColour = new Integer(getColour(sequenceFeatures[sfindex]).getRGB());//av.featuresDisplayed
547                     //.get(sequenceFeatures[sfindex].type);
548           }
549
550         }
551         else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
552         {
553
554           renderFeature(
555                   g,
556                   seq,
557                   seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
558                   seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
559                   new Color(((Integer) av.featuresDisplayed
560                           .get(sequenceFeatures[sfindex].type)).intValue()),
561                   start, end, y1);
562           renderFeature(
563                   g,
564                   seq,
565                   seq.findIndex(sequenceFeatures[sfindex].end) - 1,
566                   seq.findIndex(sequenceFeatures[sfindex].end) - 1,
567                   new Color(((Integer) av.featuresDisplayed
568                           .get(sequenceFeatures[sfindex].type)).intValue()),
569                   start, end, y1);
570
571         }
572         else
573         {
574           renderFeature(g, seq, seq
575                   .findIndex(sequenceFeatures[sfindex].begin) - 1, seq
576                   .findIndex(sequenceFeatures[sfindex].end) - 1,
577                   getColour(sequenceFeatures[sfindex]), start, end, y1);
578         }
579
580       }
581     }
582
583     if (transparencySetter != null && g != null)
584     {
585       transparencySetter.setTransparency(g, 1.0f);
586     }
587   }
588
589   char s;
590
591   int i;
592
593   void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
594           Color featureColour, int start, int end, int y1)
595   {
596
597     if (((fstart <= end) && (fend >= start)))
598     {
599       if (fstart < start)
600       { // fix for if the feature we have starts before the sequence start,
601         fstart = start; // but the feature end is still valid!!
602       }
603
604       if (fend >= end)
605       {
606         fend = end;
607       }
608
609       for (i = fstart; i <= fend; i++)
610       {
611         s = seq.getCharAt(i);
612
613         if (jalview.util.Comparison.isGap(s))
614         {
615           continue;
616         }
617
618         g.setColor(featureColour);
619
620         g.fillRect((i - start) * av.charWidth, y1, av.charWidth,
621                 av.charHeight);
622
623         if (!av.validCharWidth)
624         {
625           continue;
626         }
627
628         g.setColor(Color.white);
629         charOffset = (av.charWidth - fm.charWidth(s)) / 2;
630         g.drawString(String.valueOf(s), charOffset
631                 + (av.charWidth * (i - start)), (y1 + av.charHeight)
632                 - av.charHeight / 5); // pady = height / 5;
633
634       }
635     }
636   }
637   Hashtable minmax=null;
638   void findAllFeatures()
639   {
640     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
641
642     av.featuresDisplayed = new Hashtable();
643     Vector allfeatures = new Vector();
644     minmax = new Hashtable();
645     
646     for (int i = 0; i < av.alignment.getHeight(); i++)
647     {
648       SequenceFeature[] features = av.alignment.getSequenceAt(i)
649               .getSequenceFeatures();
650
651       if (features == null)
652       {
653         continue;
654       }
655
656       int index = 0;
657       while (index < features.length)
658       {
659         if (features[index].begin==0 && features[index].end==0) {
660           index++;
661           continue;
662         }
663         if (!av.featuresDisplayed.containsKey(features[index].getType()))
664         {
665           if (getColour(features[index].getType()) == null)
666           {
667             featureColours.put(features[index].getType(), ucs
668                     .createColourFromName(features[index].getType()));
669           }
670
671           av.featuresDisplayed.put(features[index].getType(), new Integer(
672                   getColour(features[index].getType()).getRGB()));
673           allfeatures.addElement(features[index].getType());
674         }
675                 if (features[index].score != Float.NaN)
676         {
677           int nonpos= features[index].getBegin()>=1 ? 0 : 1;
678           float[][] mm = (float[][]) minmax.get(features[index].getType());
679           if (mm == null)
680           {
681             mm = new float[][] {null, null };
682             minmax.put(features[index].getType(), mm);
683           }
684           if (mm[nonpos]==null)
685           { 
686             mm[nonpos] = new float[] { features[index].score, features[index].score };
687             
688           }
689           else
690           {
691             if (mm[nonpos][0] > features[index].score)
692             {
693               mm[nonpos][0] = features[index].score;
694             }
695             if (mm[nonpos][1] < features[index].score)
696             {
697               mm[nonpos][1] = features[index].score;
698             }
699           }
700         }
701         
702         index++;
703       }
704     }
705
706     renderOrder = new String[allfeatures.size()];
707     Enumeration en = allfeatures.elements();
708     int i = allfeatures.size() - 1;
709     while (en.hasMoreElements())
710     {
711       renderOrder[i] = en.nextElement().toString();
712       i--;
713     }
714   }
715
716   public Color getColour(String featureType)
717   {
718     Object fc = featureColours.get(featureType);
719     if (fc == null)
720     {
721       jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
722       Color col = ucs.createColourFromName(featureType);
723       featureColours.put(featureType, col);
724       return col;
725     }
726     else if (fc instanceof Color)
727     {
728       return (Color) fc;
729     }
730     else
731     {
732       if (fc instanceof GraduatedColor)
733       {
734         return ((GraduatedColor) fc).getMinColor();
735       }
736       // TODO: raise an implementation error here.
737       return null; // Color.white;
738     }
739   }
740   /**
741    * implement graduated colouring for features with scores
742    * 
743    * @param feature
744    * @return render colour for the given feature
745    */
746   public Color getColour(SequenceFeature feature)
747   {
748     Object fc = featureColours.get(feature.type);
749     if (fc == null)
750     {
751       jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
752       Color col = ucs.createColourFromName(feature.type);
753       featureColours.put(feature.type, col);
754       return col;
755     }
756     else if (fc instanceof Color)
757     {
758       return (Color) fc;
759     }
760     else
761     {
762       if (fc instanceof GraduatedColor)
763       {
764         return ((GraduatedColor) fc).findColor(feature);
765       }
766       // TODO: raise an implementation error here.
767       return null; // Color.white;
768     }
769   }
770
771   public void setColour(String featureType, Color col)
772   {
773     featureColours.put(featureType, col);
774   }
775
776   public void setFeaturePriority(Object[][] data)
777   {
778     // The feature table will display high priority
779     // features at the top, but theses are the ones
780     // we need to render last, so invert the data
781     if (av.featuresDisplayed != null)
782     {
783       av.featuresDisplayed.clear();
784     }
785
786     /*
787      * if (visibleNew) { if (av.featuresDisplayed != null) {
788      * av.featuresDisplayed.clear(); } else { av.featuresDisplayed = new
789      * Hashtable(); } } if (data == null) { return; }
790      */
791
792     renderOrder = new String[data.length];
793
794     if (data.length > 0)
795     {
796       for (int i = 0; i < data.length; i++)
797       {
798         String type = data[i][0].toString();
799         setColour(type, (Color) data[i][1]);
800         if (((Boolean) data[i][2]).booleanValue())
801         {
802           av.featuresDisplayed.put(type, new Integer(getColour(type)
803                   .getRGB()));
804         }
805
806         renderOrder[data.length - i - 1] = type;
807       }
808     }
809   }
810
811   /**
812    * @return a simple list of feature group names or null
813    */
814   public String[] getGroups()
815   {
816     buildGroupHash();
817     if (featureGroups != null)
818     {
819       String[] gps = new String[featureGroups.size()];
820       Enumeration gn = featureGroups.keys();
821       int i = 0;
822       while (gn.hasMoreElements())
823       {
824         gps[i++] = (String) gn.nextElement();
825       }
826       return gps;
827     }
828     return null;
829   }
830
831   /**
832    * get visible or invisible groups
833    * 
834    * @param visible
835    *                true to return visible groups, false to return hidden ones.
836    * @return list of groups
837    */
838   public String[] getGroups(boolean visible)
839   {
840     buildGroupHash();
841     if (featureGroups != null)
842     {
843       Vector gp = new Vector();
844
845       Enumeration gn = featureGroups.keys();
846       while (gn.hasMoreElements())
847       {
848         String nm = (String) gn.nextElement();
849         Boolean state = (Boolean) featureGroups.get(nm);
850         if (state.booleanValue() == visible)
851         {
852           gp.addElement(nm);
853         }
854       }
855       String[] gps = new String[gp.size()];
856       gp.copyInto(gps);
857
858       int i = 0;
859       while (gn.hasMoreElements())
860       {
861         gps[i++] = (String) gn.nextElement();
862       }
863       return gps;
864     }
865     return null;
866   }
867
868   /**
869    * set all feature groups in toset to be visible or invisible
870    * 
871    * @param toset
872    *                group names
873    * @param visible
874    *                the state of the named groups to set
875    */
876   public void setGroupState(String[] toset, boolean visible)
877   {
878     buildGroupHash();
879     if (toset != null && toset.length > 0 && featureGroups != null)
880     {
881       boolean rdrw = false;
882       for (int i = 0; i < toset.length; i++)
883       {
884         Object st = featureGroups.get(toset[i]);
885         if (st != null)
886         {
887           featureGroups.put(toset[i], new Boolean(visible));
888           rdrw = rdrw || (visible != ((Boolean) st).booleanValue());
889         }
890       }
891       if (rdrw)
892       {
893         if (this.av != null)
894           if (this.av.featureSettings != null)
895           {
896             av.featureSettings.rebuildGroups();
897             this.av.featureSettings.resetTable(true);
898           }
899           else
900           {
901             buildFeatureHash();
902           }
903         if (av != null)
904         {
905           av.alignmentChanged(null);
906         }
907       }
908     }
909   }
910
911   /**
912    * analyse alignment for groups and hash tables (used to be embedded in
913    * FeatureSettings.setTableData)
914    * 
915    * @return true if features are on the alignment
916    */
917   public boolean buildGroupHash()
918   {
919     boolean alignmentHasFeatures = false;
920     if (featureGroups == null)
921     {
922       featureGroups = new Hashtable();
923     }
924     Vector allFeatures = new Vector();
925     Vector allGroups = new Vector();
926     SequenceFeature[] tmpfeatures;
927     String group;
928     for (int i = 0; i < av.alignment.getHeight(); i++)
929     {
930       if (av.alignment.getSequenceAt(i).getSequenceFeatures() == null)
931       {
932         continue;
933       }
934
935       alignmentHasFeatures = true;
936
937       tmpfeatures = av.alignment.getSequenceAt(i).getSequenceFeatures();
938       int index = 0;
939       while (index < tmpfeatures.length)
940       {
941         if (tmpfeatures[index].getFeatureGroup() != null)
942         {
943           group = tmpfeatures[index].featureGroup;
944           if (!allGroups.contains(group))
945           {
946             allGroups.addElement(group);
947
948             boolean visible = true;
949             if (featureGroups.containsKey(group))
950             {
951               visible = ((Boolean) featureGroups.get(group)).booleanValue();
952             }
953             else
954             {
955               featureGroups.put(group, new Boolean(visible));
956             }
957           }
958         }
959
960         if (!allFeatures.contains(tmpfeatures[index].getType()))
961         {
962           allFeatures.addElement(tmpfeatures[index].getType());
963         }
964         index++;
965       }
966     }
967
968     return alignmentHasFeatures;
969   }
970
971   /**
972    * rebuild the featuresDisplayed and renderorder list based on the
973    * featureGroups hash and any existing display state and force a repaint if
974    * necessary
975    * 
976    * @return true if alignment has visible features
977    */
978   public boolean buildFeatureHash()
979   {
980     boolean alignmentHasFeatures = false;
981     if (featureGroups == null)
982     {
983       alignmentHasFeatures = buildGroupHash();
984     }
985     if (!alignmentHasFeatures)
986       return false;
987     Hashtable fdisp = av.featuresDisplayed;
988     Vector allFeatures = new Vector();
989     SequenceFeature[] tmpfeatures;
990     String group;
991     for (int i = 0; i < av.alignment.getHeight(); i++)
992     {
993       if (av.alignment.getSequenceAt(i).getSequenceFeatures() == null)
994       {
995         continue;
996       }
997
998       alignmentHasFeatures = true;
999
1000       tmpfeatures = av.alignment.getSequenceAt(i).getSequenceFeatures();
1001       int index = 0;
1002       while (index < tmpfeatures.length)
1003       {
1004         boolean visible = true;
1005         if (tmpfeatures[index].getFeatureGroup() != null)
1006         {
1007           group = tmpfeatures[index].featureGroup;
1008           if (featureGroups.containsKey(group))
1009           {
1010             visible = ((Boolean) featureGroups.get(group)).booleanValue();
1011           }
1012         }
1013
1014         if (visible && !allFeatures.contains(tmpfeatures[index].getType()))
1015         {
1016           allFeatures.addElement(tmpfeatures[index].getType());
1017         }
1018         index++;
1019       }
1020     }
1021     if (allFeatures.size() > 0)
1022     {
1023       String[] neworder = new String[allFeatures.size()];
1024       int p = neworder.length - 1;
1025       for (int i = renderOrder.length - 1; i >= 0; i--)
1026       {
1027         if (allFeatures.contains(renderOrder[i]))
1028         {
1029           neworder[p--] = renderOrder[i];
1030           allFeatures.removeElement(renderOrder[i]);
1031         }
1032         else
1033         {
1034           av.featuresDisplayed.remove(renderOrder[i]);
1035         }
1036       }
1037       for (int i = allFeatures.size() - 1; i > 0; i++)
1038       {
1039         Object e = allFeatures.elementAt(i);
1040         if (e != null)
1041         {
1042           neworder[p--] = (String) e;
1043           av.featuresDisplayed.put(e, getColour((String) e));
1044         }
1045       }
1046       renderOrder = neworder;
1047       return true;
1048     }
1049
1050     return alignmentHasFeatures;
1051   }
1052   /**
1053    * 
1054    * @return the displayed feature type as an array of strings
1055    */
1056   protected String[] getDisplayedFeatureTypes()
1057   {
1058     String[] typ = null;
1059     synchronized (renderOrder)
1060     {
1061       typ = new String[renderOrder.length];
1062       System.arraycopy(renderOrder, 0, typ, 0, typ.length);
1063       for (int i = 0; i < typ.length; i++)
1064       {
1065         if (av.featuresDisplayed.get(typ[i]) == null)
1066         {
1067           typ[i] = null;
1068             }
1069         }       
1070       }
1071     return typ;
1072   }
1073 }
1074
1075 class TransparencySetter
1076 {
1077   void setTransparency(Graphics g, float value)
1078   {
1079     Graphics2D g2 = (Graphics2D) g;
1080     g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1081             value));
1082   }
1083 }