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