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