jalview 2.5 release banner
[jalview.git] / src / jalview / appletgui / FeatureRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
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
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   void findAllFeatures()
752   {
753     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
754
755     av.featuresDisplayed = new Hashtable();
756     Vector allfeatures = new Vector();
757     minmax = new Hashtable();
758
759     for (int i = 0; i < av.alignment.getHeight(); i++)
760     {
761       SequenceFeature[] features = av.alignment.getSequenceAt(i)
762               .getSequenceFeatures();
763
764       if (features == null)
765       {
766         continue;
767       }
768
769       int index = 0;
770       while (index < features.length)
771       {
772         if (features[index].begin == 0 && features[index].end == 0)
773         {
774           index++;
775           continue;
776         }
777         if (!av.featuresDisplayed.containsKey(features[index].getType()))
778         {
779           if (getColour(features[index].getType()) == null)
780           {
781             featureColours.put(features[index].getType(), ucs
782                     .createColourFromName(features[index].getType()));
783           }
784
785           av.featuresDisplayed.put(features[index].getType(), new Integer(
786                   getColour(features[index].getType()).getRGB()));
787           allfeatures.addElement(features[index].getType());
788         }
789         if (features[index].score != Float.NaN)
790         {
791           int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
792           float[][] mm = (float[][]) minmax.get(features[index].getType());
793           if (mm == null)
794           {
795             mm = new float[][]
796             { null, null };
797             minmax.put(features[index].getType(), mm);
798           }
799           if (mm[nonpos] == null)
800           {
801             mm[nonpos] = new float[]
802             { features[index].score, features[index].score };
803
804           }
805           else
806           {
807             if (mm[nonpos][0] > features[index].score)
808             {
809               mm[nonpos][0] = features[index].score;
810             }
811             if (mm[nonpos][1] < features[index].score)
812             {
813               mm[nonpos][1] = features[index].score;
814             }
815           }
816         }
817
818         index++;
819       }
820     }
821
822     renderOrder = new String[allfeatures.size()];
823     Enumeration en = allfeatures.elements();
824     int i = allfeatures.size() - 1;
825     while (en.hasMoreElements())
826     {
827       renderOrder[i] = en.nextElement().toString();
828       i--;
829     }
830   }
831
832   /**
833    * get a feature style object for the given type string. Creates a
834    * java.awt.Color for a featureType with no existing colourscheme. TODO:
835    * replace return type with object implementing standard abstract colour/style
836    * interface
837    * 
838    * @param featureType
839    * @return java.awt.Color or GraduatedColor
840    */
841   public Object getFeatureStyle(String featureType)
842   {
843     Object fc = featureColours.get(featureType);
844     if (fc == null)
845     {
846       jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
847       Color col = ucs.createColourFromName(featureType);
848       featureColours.put(featureType, fc = col);
849     }
850     return fc;
851   }
852
853   public Color getColour(String featureType)
854   {
855     Object fc = getFeatureStyle(featureType);
856
857     if (fc instanceof Color)
858     {
859       return (Color) fc;
860     }
861     else
862     {
863       if (fc instanceof GraduatedColor)
864       {
865         return ((GraduatedColor) fc).getMaxColor();
866       }
867     }
868     throw new Error("Implementation Error: Unrecognised render object "
869             + fc.getClass() + " for features of type " + featureType);
870   }
871
872   /**
873    * 
874    * @param sequenceFeature
875    * @return true if feature is visible.
876    */
877   private boolean showFeature(SequenceFeature sequenceFeature)
878   {
879     Object fc = getFeatureStyle(sequenceFeature.type);
880     if (fc instanceof GraduatedColor)
881     {
882       return ((GraduatedColor) fc).isColored(sequenceFeature);
883     }
884     else
885     {
886       return true;
887     }
888   }
889
890   /**
891    * implement graduated colouring for features with scores
892    * 
893    * @param feature
894    * @return render colour for the given feature
895    */
896   public Color getColour(SequenceFeature feature)
897   {
898     Object fc = getFeatureStyle(feature.getType());
899     if (fc instanceof Color)
900     {
901       return (Color) fc;
902     }
903     else
904     {
905       if (fc instanceof GraduatedColor)
906       {
907         return ((GraduatedColor) fc).findColor(feature);
908       }
909     }
910     throw new Error("Implementation Error: Unrecognised render object "
911             + fc.getClass() + " for features of type " + feature.getType());
912   }
913
914   public void setColour(String featureType, Object col)
915   {
916     // overwrite
917     // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
918     // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
919     // Object c = featureColours.get(featureType);
920     // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
921     // !((GraduatedColor)c).getMaxColor().equals(_col)))
922     {
923       featureColours.put(featureType, col);
924     }
925   }
926
927   public void setFeaturePriority(Object[][] data)
928   {
929     // The feature table will display high priority
930     // features at the top, but theses are the ones
931     // we need to render last, so invert the data
932     if (av.featuresDisplayed != null)
933     {
934       av.featuresDisplayed.clear();
935     }
936
937     /*
938      * if (visibleNew) { if (av.featuresDisplayed != null) {
939      * av.featuresDisplayed.clear(); } else { av.featuresDisplayed = new
940      * Hashtable(); } } if (data == null) { return; }
941      */
942
943     renderOrder = new String[data.length];
944
945     if (data.length > 0)
946     {
947       for (int i = 0; i < data.length; i++)
948       {
949         String type = data[i][0].toString();
950         setColour(type, data[i][1]);
951         if (((Boolean) data[i][2]).booleanValue())
952         {
953           av.featuresDisplayed.put(type, new Integer(getColour(type)
954                   .getRGB()));
955         }
956
957         renderOrder[data.length - i - 1] = type;
958       }
959     }
960   }
961
962   /**
963    * @return a simple list of feature group names or null
964    */
965   public String[] getGroups()
966   {
967     buildGroupHash();
968     if (featureGroups != null)
969     {
970       String[] gps = new String[featureGroups.size()];
971       Enumeration gn = featureGroups.keys();
972       int i = 0;
973       while (gn.hasMoreElements())
974       {
975         gps[i++] = (String) gn.nextElement();
976       }
977       return gps;
978     }
979     return null;
980   }
981
982   /**
983    * get visible or invisible groups
984    * 
985    * @param visible
986    *          true to return visible groups, false to return hidden ones.
987    * @return list of groups
988    */
989   public String[] getGroups(boolean visible)
990   {
991     buildGroupHash();
992     if (featureGroups != null)
993     {
994       Vector gp = new Vector();
995
996       Enumeration gn = featureGroups.keys();
997       while (gn.hasMoreElements())
998       {
999         String nm = (String) gn.nextElement();
1000         Boolean state = (Boolean) featureGroups.get(nm);
1001         if (state.booleanValue() == visible)
1002         {
1003           gp.addElement(nm);
1004         }
1005       }
1006       String[] gps = new String[gp.size()];
1007       gp.copyInto(gps);
1008
1009       int i = 0;
1010       while (gn.hasMoreElements())
1011       {
1012         gps[i++] = (String) gn.nextElement();
1013       }
1014       return gps;
1015     }
1016     return null;
1017   }
1018
1019   /**
1020    * set all feature groups in toset to be visible or invisible
1021    * 
1022    * @param toset
1023    *          group names
1024    * @param visible
1025    *          the state of the named groups to set
1026    */
1027   public void setGroupState(String[] toset, boolean visible)
1028   {
1029     buildGroupHash();
1030     if (toset != null && toset.length > 0 && featureGroups != null)
1031     {
1032       boolean rdrw = false;
1033       for (int i = 0; i < toset.length; i++)
1034       {
1035         Object st = featureGroups.get(toset[i]);
1036         if (st != null)
1037         {
1038           featureGroups.put(toset[i], new Boolean(visible));
1039           rdrw = rdrw || (visible != ((Boolean) st).booleanValue());
1040         }
1041       }
1042       if (rdrw)
1043       {
1044         if (this.av != null)
1045           if (this.av.featureSettings != null)
1046           {
1047             av.featureSettings.rebuildGroups();
1048             this.av.featureSettings.resetTable(true);
1049           }
1050           else
1051           {
1052             buildFeatureHash();
1053           }
1054         if (av != null)
1055         {
1056           av.alignmentChanged(null);
1057         }
1058       }
1059     }
1060   }
1061
1062   /**
1063    * analyse alignment for groups and hash tables (used to be embedded in
1064    * FeatureSettings.setTableData)
1065    * 
1066    * @return true if features are on the alignment
1067    */
1068   public boolean buildGroupHash()
1069   {
1070     boolean alignmentHasFeatures = false;
1071     if (featureGroups == null)
1072     {
1073       featureGroups = new Hashtable();
1074     }
1075     Vector allFeatures = new Vector();
1076     Vector allGroups = new Vector();
1077     SequenceFeature[] tmpfeatures;
1078     String group;
1079     for (int i = 0; i < av.alignment.getHeight(); i++)
1080     {
1081       if (av.alignment.getSequenceAt(i).getSequenceFeatures() == null)
1082       {
1083         continue;
1084       }
1085
1086       alignmentHasFeatures = true;
1087
1088       tmpfeatures = av.alignment.getSequenceAt(i).getSequenceFeatures();
1089       int index = 0;
1090       while (index < tmpfeatures.length)
1091       {
1092         if (tmpfeatures[index].getFeatureGroup() != null)
1093         {
1094           group = tmpfeatures[index].featureGroup;
1095           if (!allGroups.contains(group))
1096           {
1097             allGroups.addElement(group);
1098
1099             boolean visible = true;
1100             if (featureGroups.containsKey(group))
1101             {
1102               visible = ((Boolean) featureGroups.get(group)).booleanValue();
1103             }
1104             else
1105             {
1106               featureGroups.put(group, new Boolean(visible));
1107             }
1108           }
1109         }
1110
1111         if (!allFeatures.contains(tmpfeatures[index].getType()))
1112         {
1113           allFeatures.addElement(tmpfeatures[index].getType());
1114         }
1115         index++;
1116       }
1117     }
1118
1119     return alignmentHasFeatures;
1120   }
1121
1122   /**
1123    * rebuild the featuresDisplayed and renderorder list based on the
1124    * featureGroups hash and any existing display state and force a repaint if
1125    * necessary
1126    * 
1127    * @return true if alignment has visible features
1128    */
1129   public boolean buildFeatureHash()
1130   {
1131     boolean alignmentHasFeatures = false;
1132     if (featureGroups == null)
1133     {
1134       alignmentHasFeatures = buildGroupHash();
1135     }
1136     if (!alignmentHasFeatures)
1137       return false;
1138     Hashtable fdisp = av.featuresDisplayed;
1139     Vector allFeatures = new Vector();
1140     SequenceFeature[] tmpfeatures;
1141     String group;
1142     for (int i = 0; i < av.alignment.getHeight(); i++)
1143     {
1144       if (av.alignment.getSequenceAt(i).getSequenceFeatures() == null)
1145       {
1146         continue;
1147       }
1148
1149       alignmentHasFeatures = true;
1150
1151       tmpfeatures = av.alignment.getSequenceAt(i).getSequenceFeatures();
1152       int index = 0;
1153       while (index < tmpfeatures.length)
1154       {
1155         boolean visible = true;
1156         if (tmpfeatures[index].getFeatureGroup() != null)
1157         {
1158           group = tmpfeatures[index].featureGroup;
1159           if (featureGroups.containsKey(group))
1160           {
1161             visible = ((Boolean) featureGroups.get(group)).booleanValue();
1162           }
1163         }
1164
1165         if (visible && !allFeatures.contains(tmpfeatures[index].getType()))
1166         {
1167           allFeatures.addElement(tmpfeatures[index].getType());
1168         }
1169         index++;
1170       }
1171     }
1172     if (allFeatures.size() > 0)
1173     {
1174       String[] neworder = new String[allFeatures.size()];
1175       int p = neworder.length - 1;
1176       for (int i = renderOrder.length - 1; i >= 0; i--)
1177       {
1178         if (allFeatures.contains(renderOrder[i]))
1179         {
1180           neworder[p--] = renderOrder[i];
1181           allFeatures.removeElement(renderOrder[i]);
1182         }
1183         else
1184         {
1185           av.featuresDisplayed.remove(renderOrder[i]);
1186         }
1187       }
1188       for (int i = allFeatures.size() - 1; i > 0; i++)
1189       {
1190         Object e = allFeatures.elementAt(i);
1191         if (e != null)
1192         {
1193           neworder[p--] = (String) e;
1194           av.featuresDisplayed.put(e, getColour((String) e));
1195         }
1196       }
1197       renderOrder = neworder;
1198       return true;
1199     }
1200
1201     return alignmentHasFeatures;
1202   }
1203
1204   /**
1205    * 
1206    * @return the displayed feature type as an array of strings
1207    */
1208   protected String[] getDisplayedFeatureTypes()
1209   {
1210     String[] typ = null;
1211     synchronized (renderOrder)
1212     {
1213       typ = new String[renderOrder.length];
1214       System.arraycopy(renderOrder, 0, typ, 0, typ.length);
1215       for (int i = 0; i < typ.length; i++)
1216       {
1217         if (av.featuresDisplayed.get(typ[i]) == null)
1218         {
1219           typ[i] = null;
1220         }
1221       }
1222     }
1223     return typ;
1224   }
1225 }
1226
1227 class TransparencySetter
1228 {
1229   void setTransparency(Graphics g, float value)
1230   {
1231     Graphics2D g2 = (Graphics2D) g;
1232     g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1233             value));
1234   }
1235 }