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