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