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