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