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