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