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