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