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