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