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