gui/debug: integrate graduated feature colour editor into amend feature and feature...
[jalview.git] / src / jalview / gui / 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.gui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import java.awt.image.*;
26 import java.beans.PropertyChangeListener;
27 import java.beans.PropertyChangeSupport;
28
29 import javax.swing.*;
30
31 import jalview.datamodel.*;
32 import jalview.schemes.GraduatedColor;
33 import jalview.util.FeatureColourI;
34
35 /**
36  * DOCUMENT ME!
37  * 
38  * @author $author$
39  * @version $Revision$
40  */
41 public class FeatureRenderer
42 {
43   AlignmentPanel ap;
44
45   AlignViewport av;
46
47   Color resBoxColour;
48
49   /**
50    * global transparency for feature
51    */
52   float transparency = 1.0f;
53
54   FontMetrics fm;
55
56   int charOffset;
57
58   Hashtable featureColours = new Hashtable();
59
60   // A higher level for grouping features of a
61   // particular type
62   Hashtable featureGroups = new Hashtable();
63
64   // This is actually an Integer held in the hashtable,
65   // Retrieved using the key feature type
66   Object currentColour;
67
68   String[] renderOrder;
69
70   PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
71
72   Vector allfeatures;
73
74   /**
75    * Creates a new FeatureRenderer object.
76    * 
77    * @param av
78    *                DOCUMENT ME!
79    */
80   public FeatureRenderer(AlignmentPanel ap)
81   {
82     this.ap = ap;
83     this.av = ap.av;
84   }
85
86   public class FeatureRendererSettings implements Cloneable
87   {
88     String[] renderOrder;
89
90     Hashtable featureGroups;
91
92     Hashtable featureColours;
93
94     float transparency;
95
96     Hashtable featureOrder;
97
98     public FeatureRendererSettings(String[] renderOrder,
99             Hashtable featureGroups, Hashtable featureColours,
100             float transparency, Hashtable featureOrder)
101     {
102       super();
103       this.renderOrder = renderOrder;
104       this.featureGroups = featureGroups;
105       this.featureColours = featureColours;
106       this.transparency = transparency;
107       this.featureOrder = featureOrder;
108     }
109
110     public FeatureRendererSettings(FeatureRenderer fr)
111     {
112       this.renderOrder = fr.renderOrder;
113       this.featureGroups = fr.featureGroups;
114       this.featureColours = fr.featureColours;
115       this.transparency = fr.transparency;
116       this.featureOrder = fr.featureOrder;
117     }
118   }
119
120   public FeatureRendererSettings getSettings()
121   {
122     return new FeatureRendererSettings(this);
123   }
124
125   public void transferSettings(FeatureRendererSettings fr)
126   {
127     this.renderOrder = fr.renderOrder;
128     this.featureGroups = fr.featureGroups;
129     this.featureColours = fr.featureColours;
130     this.transparency = fr.transparency;
131     this.featureOrder = fr.featureOrder;
132   }
133
134   public void transferSettings(FeatureRenderer fr)
135   {
136     this.renderOrder = fr.renderOrder;
137     this.featureGroups = fr.featureGroups;
138     this.featureColours = fr.featureColours;
139     this.transparency = fr.transparency;
140     this.featureOrder = fr.featureOrder;
141   }
142
143   BufferedImage offscreenImage;
144
145   boolean offscreenRender = false;
146
147   public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
148   {
149     return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
150   }
151
152   /**
153    * This is used by the Molecule Viewer and Overview to get the accurate
154    * colourof the rendered sequence
155    */
156   public int findFeatureColour(int initialCol, SequenceI seq, int column)
157   {
158     if (!av.showSequenceFeatures)
159     {
160       return initialCol;
161     }
162
163     if (seq != lastSeq)
164     {
165       lastSeq = seq;
166       sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
167       if (sequenceFeatures != null)
168       {
169         sfSize = sequenceFeatures.length;
170       }
171     }
172
173     if (sequenceFeatures != lastSeq.getDatasetSequence()
174             .getSequenceFeatures())
175     {
176       sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
177       if (sequenceFeatures != null)
178       {
179         sfSize = sequenceFeatures.length;
180       }
181     }
182
183     if (sequenceFeatures == null || sfSize == 0)
184     {
185       return initialCol;
186     }
187
188     if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
189     {
190       return Color.white.getRGB();
191     }
192
193     // Only bother making an offscreen image if transparency is applied
194     if (transparency != 1.0f && offscreenImage == null)
195     {
196       offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
197     }
198
199     currentColour = null;
200
201     offscreenRender = true;
202
203     if (offscreenImage != null)
204     {
205       offscreenImage.setRGB(0, 0, initialCol);
206       drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
207
208       return offscreenImage.getRGB(0, 0);
209     }
210     else
211     {
212       drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
213
214       if (currentColour == null)
215       {
216         return initialCol;
217       }
218       else
219       {
220         return ((Integer) currentColour).intValue();
221       }
222     }
223
224   }
225
226   /**
227    * DOCUMENT ME!
228    * 
229    * @param g
230    *                DOCUMENT ME!
231    * @param seq
232    *                DOCUMENT ME!
233    * @param sg
234    *                DOCUMENT ME!
235    * @param start
236    *                DOCUMENT ME!
237    * @param end
238    *                DOCUMENT ME!
239    * @param x1
240    *                DOCUMENT ME!
241    * @param y1
242    *                DOCUMENT ME!
243    * @param width
244    *                DOCUMENT ME!
245    * @param height
246    *                DOCUMENT ME!
247    */
248   // String type;
249   // SequenceFeature sf;
250   SequenceI lastSeq;
251
252   SequenceFeature[] sequenceFeatures;
253
254   int sfSize, sfindex, spos, epos;
255
256   /**
257    * show scores as heights
258    */
259   protected boolean varyHeight = false;
260
261   synchronized public void drawSequence(Graphics g, SequenceI seq,
262           int start, int end, int y1)
263   {
264
265     if (seq.getDatasetSequence().getSequenceFeatures() == null
266             || seq.getDatasetSequence().getSequenceFeatures().length == 0)
267     {
268       return;
269     }
270
271     if (g != null)
272     {
273       fm = g.getFontMetrics();
274     }
275
276     if (av.featuresDisplayed == null || renderOrder == null
277             || newFeatureAdded)
278     {
279       findAllFeatures();
280       if (av.featuresDisplayed.size() < 1)
281       {
282         return;
283       }
284
285       sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
286     }
287
288     if (lastSeq == null
289             || seq != lastSeq
290             || seq.getDatasetSequence().getSequenceFeatures() != sequenceFeatures)
291     {
292       lastSeq = seq;
293       sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
294     }
295
296     if (transparency != 1 && g != null)
297     {
298       Graphics2D g2 = (Graphics2D) g;
299       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
300               transparency));
301     }
302
303     if (!offscreenRender)
304     {
305       spos = lastSeq.findPosition(start);
306       epos = lastSeq.findPosition(end);
307     }
308
309     sfSize = sequenceFeatures.length;
310     String type;
311     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
312     {
313       type = renderOrder[renderIndex];
314
315       if (type == null || !av.featuresDisplayed.containsKey(type))
316       {
317         continue;
318       }
319
320       // loop through all features in sequence to find
321       // current feature to render
322       for (sfindex = 0; sfindex < sfSize; sfindex++)
323       {
324         if (!sequenceFeatures[sfindex].type.equals(type))
325         {
326           continue;
327         }
328
329         if (featureGroups != null
330                 && sequenceFeatures[sfindex].featureGroup != null
331                 && sequenceFeatures[sfindex].featureGroup.length() != 0
332                 && featureGroups
333                         .containsKey(sequenceFeatures[sfindex].featureGroup)
334                 && !((Boolean) featureGroups
335                         .get(sequenceFeatures[sfindex].featureGroup))
336                         .booleanValue())
337         {
338           continue;
339         }
340
341         if (!offscreenRender
342                 && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
343                         .getEnd() < spos))
344         {
345           continue;
346         }
347
348         if (offscreenRender && offscreenImage == null)
349         {
350           if (sequenceFeatures[sfindex].begin <= start
351                   && sequenceFeatures[sfindex].end >= start)
352           {
353             // this is passed out to the overview and other sequence renderers (e.g. molecule viewer) to get displayed colour for rendered sequence
354             currentColour = new Integer(getColour(sequenceFeatures[sfindex]).getRGB());
355              // used to be retreived from av.featuresDisplayed
356             // currentColour = av.featuresDisplayed
357             //        .get(sequenceFeatures[sfindex].type);
358              
359             
360           }
361         }
362         else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
363         {
364
365           renderFeature(
366                   g,
367                   seq,
368                   seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
369                   seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
370                   getColour(sequenceFeatures[sfindex])
371                   //new Color(((Integer) av.featuresDisplayed
372                   //        .get(sequenceFeatures[sfindex].type)).intValue())
373                   ,
374                   start, end, y1);
375           renderFeature(
376                   g,
377                   seq,
378                   seq.findIndex(sequenceFeatures[sfindex].end) - 1,
379                   seq.findIndex(sequenceFeatures[sfindex].end) - 1,
380                   getColour(sequenceFeatures[sfindex])
381                   //new Color(((Integer) av.featuresDisplayed
382                   //        .get(sequenceFeatures[sfindex].type)).intValue())
383                   ,
384                   start, end, y1);
385
386         }
387         else
388           if (showFeature(sequenceFeatures[sfindex]))
389           {
390               if (av.showSeqFeaturesHeight
391                       && sequenceFeatures[sfindex].score != Float.NaN)
392               {
393                 renderScoreFeature(g, seq, seq
394                         .findIndex(sequenceFeatures[sfindex].begin) - 1,
395                         seq.findIndex(sequenceFeatures[sfindex].end) - 1,
396                         getColour(sequenceFeatures[sfindex]), start, end,
397                         y1, normaliseScore(sequenceFeatures[sfindex]));
398               }
399               else
400               {
401                 renderFeature(g, seq, seq
402                         .findIndex(sequenceFeatures[sfindex].begin) - 1,
403                         seq.findIndex(sequenceFeatures[sfindex].end) - 1,
404                         getColour(sequenceFeatures[sfindex]), start, end,
405                         y1);
406               }
407           }
408
409       }
410
411     }
412
413     if (transparency != 1.0f && g != null)
414     {
415       Graphics2D g2 = (Graphics2D) g;
416       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
417               1.0f));
418     }
419   }
420
421   Hashtable minmax = new Hashtable();
422
423   /**
424    * normalise a score against the max/min bounds for the feature type.
425    * 
426    * @param sequenceFeature
427    * @return byte[] { signed, normalised signed (-127 to 127) or unsigned
428    *         (0-255) value.
429    */
430   private final byte[] normaliseScore(SequenceFeature sequenceFeature)
431   {
432     float[] mm = ((float[][]) minmax.get(sequenceFeature.type))[0];
433     final byte[] r = new byte[]
434     { 0, (byte) 255 };
435     if (mm != null)
436     {
437       if (r[0] != 0 || mm[0] < 0.0)
438       {
439         r[0] = 1;
440         r[1] = (byte) ((int) 128.0 + 127.0 * (sequenceFeature.score / mm[1]));
441       }
442       else
443       {
444         r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1]));
445       }
446     }
447     return r;
448   }
449
450   char s;
451
452   int i;
453
454   void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
455           Color featureColour, int start, int end, int y1)
456   {
457
458     if (((fstart <= end) && (fend >= start)))
459     {
460       if (fstart < start)
461       { // fix for if the feature we have starts before the sequence start,
462         fstart = start; // but the feature end is still valid!!
463       }
464
465       if (fend >= end)
466       {
467         fend = end;
468       }
469       int pady = (y1 + av.charHeight) - av.charHeight / 5;
470       for (i = fstart; i <= fend; i++)
471       {
472         s = seq.getCharAt(i);
473
474         if (jalview.util.Comparison.isGap(s))
475         {
476           continue;
477         }
478
479         g.setColor(featureColour);
480
481         g.fillRect((i - start) * av.charWidth, y1, av.charWidth,
482                 av.charHeight);
483
484         if (offscreenRender || !av.validCharWidth)
485         {
486           continue;
487         }
488
489         g.setColor(Color.white);
490         charOffset = (av.charWidth - fm.charWidth(s)) / 2;
491         g.drawString(String.valueOf(s), charOffset
492                 + (av.charWidth * (i - start)), pady);
493
494       }
495     }
496   }
497
498   void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
499           Color featureColour, int start, int end, int y1, byte[] bs)
500   {
501
502     if (((fstart <= end) && (fend >= start)))
503     {
504       if (fstart < start)
505       { // fix for if the feature we have starts before the sequence start,
506         fstart = start; // but the feature end is still valid!!
507       }
508
509       if (fend >= end)
510       {
511         fend = end;
512       }
513       int pady = (y1 + av.charHeight) - av.charHeight / 5;
514       int ystrt = 0, yend = av.charHeight;
515       if (bs[0] != 0)
516       {
517         // signed - zero is always middle of residue line.
518         if (bs[1] < 128)
519         {
520           yend = av.charHeight * (128 - bs[1]) / 512;
521           ystrt = av.charHeight - yend / 2;
522         }
523         else
524         {
525           ystrt = av.charHeight / 2;
526           yend = av.charHeight * (bs[1] - 128) / 512;
527         }
528       }
529       else
530       {
531         yend = av.charHeight * bs[1] / 255;
532         ystrt = av.charHeight - yend;
533
534       }
535       for (i = fstart; i <= fend; i++)
536       {
537         s = seq.getCharAt(i);
538
539         if (jalview.util.Comparison.isGap(s))
540         {
541           continue;
542         }
543
544         g.setColor(featureColour);
545         int x = (i - start) * av.charWidth;
546         g.drawRect(x, y1, av.charWidth, av.charHeight);
547         g.fillRect(x, y1 + ystrt, av.charWidth, yend);
548
549         if (offscreenRender || !av.validCharWidth)
550         {
551           continue;
552         }
553
554         g.setColor(Color.black);
555         charOffset = (av.charWidth - fm.charWidth(s)) / 2;
556         g.drawString(String.valueOf(s), charOffset
557                 + (av.charWidth * (i - start)), pady);
558
559       }
560     }
561   }
562
563   boolean newFeatureAdded = false;
564
565   /**
566    * Called when alignment in associated view has new/modified features to
567    * discover and display.
568    * 
569    */
570   public void featuresAdded()
571   {
572     lastSeq = null;
573     findAllFeatures();
574   }
575
576   boolean findingFeatures = false;
577
578   /**
579    * search the alignment for all new features, give them a colour and display
580    * them. Then fires a PropertyChangeEvent on the changeSupport object.
581    * 
582    */
583   void findAllFeatures()
584   {
585     synchronized (firing)
586     {
587       if (firing.equals(Boolean.FALSE))
588       {
589         firing = Boolean.TRUE;
590         findAllFeatures(true); // add all new features as visible
591         changeSupport.firePropertyChange("changeSupport", null, null);
592         firing = Boolean.FALSE;
593       }
594     }
595   }
596
597   /**
598    * Searches alignment for all features and updates colours
599    * 
600    * @param newMadeVisible
601    *                if true newly added feature types will be rendered
602    *                immediatly
603    */
604   synchronized void findAllFeatures(boolean newMadeVisible)
605   {
606     newFeatureAdded = false;
607
608     if (findingFeatures)
609     {
610       newFeatureAdded = true;
611       return;
612     }
613
614     findingFeatures = true;
615
616     if (av.featuresDisplayed == null)
617     {
618       av.featuresDisplayed = new Hashtable();
619     }
620
621     allfeatures = new Vector();
622     Vector oldfeatures = new Vector();
623     if (renderOrder != null)
624     {
625       for (int i = 0; i < renderOrder.length; i++)
626       {
627         if (renderOrder[i] != null)
628         {
629           oldfeatures.addElement(renderOrder[i]);
630         }
631       }
632     }
633     if (minmax == null)
634     {
635       minmax = new Hashtable();
636     }
637     for (int i = 0; i < av.alignment.getHeight(); i++)
638     {
639       SequenceFeature[] features = av.alignment.getSequenceAt(i)
640               .getDatasetSequence().getSequenceFeatures();
641
642       if (features == null)
643       {
644         continue;
645       }
646
647       int index = 0;
648       while (index < features.length)
649       {
650         if (!av.featuresDisplayed.containsKey(features[index].getType()))
651         {
652
653           if (featureGroups.containsKey(features[index].getType()))
654           {
655             boolean visible = ((Boolean) featureGroups
656                     .get(features[index].featureGroup)).booleanValue();
657
658             if (!visible)
659             {
660               index++;
661               continue;
662             }
663           }
664
665           if (!(features[index].begin == 0 && features[index].end == 0))
666           {
667             // If beginning and end are 0, the feature is for the whole sequence
668             // and we don't want to render the feature in the normal way
669
670             if (newMadeVisible
671                     && !oldfeatures.contains(features[index].getType()))
672             {
673               // this is a new feature type on the alignment. Mark it for
674               // display.
675               av.featuresDisplayed.put(features[index].getType(),
676                       new Integer(getColour(features[index].getType())
677                               .getRGB()));
678               setOrder(features[index].getType(), 0);
679             }
680           }
681         }
682         if (!allfeatures.contains(features[index].getType()))
683         {
684           allfeatures.addElement(features[index].getType());
685         }
686         if (features[index].score != Float.NaN)
687         {
688           int nonpos= features[index].getBegin()>=1 ? 0 : 1;
689           float[][] mm = (float[][]) minmax.get(features[index].getType());
690           if (mm == null)
691           {
692             mm = new float[][] {null, null };
693             minmax.put(features[index].getType(), mm);
694           }
695           if (mm[nonpos]==null)
696           { 
697             mm[nonpos] = new float[] { features[index].score, features[index].score };
698             
699           }
700           else
701           {
702             if (mm[nonpos][0] > features[index].score)
703             {
704               mm[nonpos][0] = features[index].score;
705             }
706             if (mm[nonpos][1] < features[index].score)
707             {
708               mm[nonpos][1] = features[index].score;
709             }
710           }
711         }
712         index++;
713       }
714     }
715     updateRenderOrder(allfeatures);
716     findingFeatures = false;
717   }
718
719   protected Boolean firing = Boolean.FALSE;
720
721   /**
722    * replaces the current renderOrder with the unordered features in
723    * allfeatures. The ordering of any types in both renderOrder and allfeatures
724    * is preserved, and all new feature types are rendered on top of the existing
725    * types, in the order given by getOrder or the order given in allFeatures.
726    * Note. this operates directly on the featureOrder hash for efficiency. TODO:
727    * eliminate the float storage for computing/recalling the persistent ordering
728    * New Cability: updates min/max for colourscheme range if its dynamic
729    * @param allFeatures
730    */
731   private void updateRenderOrder(Vector allFeatures)
732   {
733     Vector allfeatures = new Vector(allFeatures);
734     String[] oldRender = renderOrder;
735     renderOrder = new String[allfeatures.size()];
736     Object mmrange,fc=null;
737     boolean initOrders = (featureOrder == null);
738     int opos = 0;
739     if (oldRender != null && oldRender.length > 0)
740     {
741       for (int j = 0; j < oldRender.length; j++)
742       {
743         if (oldRender[j] != null)
744         {
745           if (initOrders)
746           {
747             setOrder(oldRender[j], (1 - (1 + (float) j)
748                     / (float) oldRender.length));
749           }
750           if (allfeatures.contains(oldRender[j]))
751           {
752             renderOrder[opos++] = oldRender[j]; // existing features always
753             // appear below new features
754             allfeatures.removeElement(oldRender[j]);
755             if (minmax!=null)
756             {
757               mmrange = minmax.get(oldRender[j]);
758               if (mmrange!=null)
759               {
760                 fc = featureColours.get(oldRender[j]);
761                 if (fc!=null && fc instanceof GraduatedColor && ((GraduatedColor)fc).isAutoScale())
762                 {
763                   ((GraduatedColor) fc).updateBounds(((float[][])mmrange)[0][0],((float[][])mmrange)[0][1]);
764                 }
765             }
766           }
767         }
768       }
769       }
770     }
771     if (allfeatures.size() == 0)
772     {
773       // no new features - leave order unchanged.
774       return;
775     }
776     int i = allfeatures.size() - 1;
777     int iSize = i;
778     boolean sort = false;
779     String[] newf = new String[allfeatures.size()];
780     float[] sortOrder = new float[allfeatures.size()];
781     Enumeration en = allfeatures.elements();
782     // sort remaining elements
783     while (en.hasMoreElements())
784     {
785       newf[i] = en.nextElement().toString();
786       if (minmax!=null)
787       {
788         // update from new features minmax if necessary
789         mmrange = minmax.get(newf[i]);
790         if (mmrange!=null) 
791         {
792           fc = featureColours.get(newf[i]);
793           if (fc!=null && fc instanceof GraduatedColor && ((GraduatedColor)fc).isAutoScale())
794           {
795             ((GraduatedColor) fc).updateBounds(((float[][])mmrange)[0][0],((float[][])mmrange)[0][1]);
796           }
797         }
798       }
799       if (initOrders || !featureOrder.containsKey(newf[i]))
800       {
801         int denom = initOrders ? allfeatures.size() : featureOrder.size();
802         // new unordered feature - compute persistent ordering at head of
803         // existing features.
804         setOrder(newf[i], i / (float) denom);
805       }
806       // set order from newly found feature from persisted ordering.
807       sortOrder[i] = 2 - ((Float) featureOrder.get(newf[i])).floatValue();
808       if (i < iSize)
809       {
810         // only sort if we need to
811         sort = sort || sortOrder[i] > sortOrder[i + 1];
812       }
813       i--;
814     }
815     if (iSize > 1 && sort)
816     {
817       jalview.util.QuickSort.sort(sortOrder, newf);
818     }
819     sortOrder = null;
820     System.arraycopy(newf, 0, renderOrder, opos, newf.length);
821   }
822   /**
823    * get a feature style object for the given type string.
824    * Creates a java.awt.Color for a featureType with no existing
825    * colourscheme.
826    * TODO: replace return type with object implementing standard abstract colour/style interface
827    * @param featureType
828    * @return java.awt.Color or GraduatedColor
829    */
830   public Object getFeatureStyle(String featureType) {
831     Object fc = featureColours.get(featureType);
832     if (fc == null)
833     {
834       jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
835       Color col = ucs.createColourFromName(featureType);
836       featureColours.put(featureType, fc = col);
837     }
838     return fc;
839   }
840   /**
841    * return a nominal colour for this feature
842    * @param featureType
843    * @return standard color, or maximum colour for graduated colourscheme
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 "+fc.getClass()+" for features of type "+featureType);
861   }
862
863   /**
864    * calculate the render colour for a specific feature using current feature settings.
865    * 
866    * @param feature
867    * @return render colour for the given feature
868    */
869   public Color getColour(SequenceFeature feature)
870   {
871     Object fc = getFeatureStyle(feature.getType());
872     if (fc instanceof Color)
873     {
874       return (Color) fc;
875     }
876     else
877     {
878       if (fc instanceof GraduatedColor)
879       {
880         return ((GraduatedColor) fc).findColor(feature);
881       }
882     }
883     throw new Error("Implementation Error: Unrecognised render object "+fc.getClass()+" for features of type "+feature.getType());
884   }
885   private boolean showFeature(SequenceFeature sequenceFeature)
886   {
887     Object fc = getFeatureStyle(sequenceFeature.type);
888     if (fc instanceof GraduatedColor)
889     {
890       return ((GraduatedColor) fc).isColored(sequenceFeature);
891     } else { return true; }
892   }
893
894
895   static String lastFeatureAdded;
896
897   static String lastFeatureGroupAdded;
898
899   static String lastDescriptionAdded;
900   Object oldcol,fcol;
901   int featureIndex = 0;
902
903   boolean amendFeatures(final SequenceI[] sequences,
904           final SequenceFeature[] features, boolean newFeatures,
905           final AlignmentPanel ap)
906   {
907
908     featureIndex = 0;
909
910     final JPanel bigPanel = new JPanel(new BorderLayout());
911     final JComboBox overlaps;
912     final JTextField name = new JTextField(25);
913     final JTextField source = new JTextField(25);
914     final JTextArea description = new JTextArea(3, 25);
915     final JSpinner start = new JSpinner();
916     final JSpinner end = new JSpinner();
917     start.setPreferredSize(new Dimension(80, 20));
918     end.setPreferredSize(new Dimension(80, 20));
919     final FeatureRenderer me = this;
920     final JLabel colour = new JLabel();
921     colour.setOpaque(true);
922     //colour.setBorder(BorderFactory.createEtchedBorder());
923     colour.setMaximumSize(new Dimension(30, 16));
924     colour.addMouseListener(new MouseAdapter()
925     {
926       FeatureColourChooser fcc =null;
927       public void mousePressed(MouseEvent evt)
928       {
929         if (fcol instanceof Color)
930         {
931         Color col = JColorChooser.showDialog(Desktop.desktop,
932                 "Select Feature Colour", ((Color)fcol));
933         if (col != null)
934         {
935           fcol = col;
936           updateColourButton(bigPanel,colour,col);
937         }
938         } else {
939           
940           if (fcc==null) {
941             final String type =  features[featureIndex].getType();
942             fcc = new FeatureColourChooser(me, type);
943             fcc.setRequestFocusEnabled(true);
944             fcc.requestFocus();
945
946             fcc.addActionListener(new ActionListener() {
947
948               public void actionPerformed(ActionEvent e)
949               {
950                 fcol = fcc.getLastColour();
951                 fcc = null;
952                 setColour(type, fcol);
953                 updateColourButton(bigPanel,colour,fcol);
954               }
955             }
956             );
957             
958           }
959         }
960       }
961     });
962     JPanel tmp = new JPanel();
963     JPanel panel = new JPanel(new GridLayout(3, 1));
964
965     // /////////////////////////////////////
966     // /MULTIPLE FEATURES AT SELECTED RESIDUE
967     if (!newFeatures && features.length > 1)
968     {
969       panel = new JPanel(new GridLayout(4, 1));
970       tmp = new JPanel();
971       tmp.add(new JLabel("Select Feature: "));
972       overlaps = new JComboBox();
973       for (int i = 0; i < features.length; i++)
974       {
975         overlaps.addItem(features[i].getType() + "/"
976                 + features[i].getBegin() + "-" + features[i].getEnd()
977                 + " (" + features[i].getFeatureGroup() + ")");
978       }
979
980       tmp.add(overlaps);
981
982       overlaps.addItemListener(new ItemListener()
983       {
984         public void itemStateChanged(ItemEvent e)
985         {
986           int index = overlaps.getSelectedIndex();
987           if (index != -1)
988           {
989             featureIndex = index;
990             name.setText(features[index].getType());
991             description.setText(features[index].getDescription());
992             source.setText(features[index].getFeatureGroup());
993             start.setValue(new Integer(features[index].getBegin()));
994             end.setValue(new Integer(features[index].getEnd()));
995
996             SearchResults highlight = new SearchResults();
997             highlight.addResult(sequences[0], features[index].getBegin(),
998                     features[index].getEnd());
999
1000             ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
1001
1002           }
1003           Object col = getFeatureStyle(name.getText());
1004           if (col == null)
1005           {
1006             col = new jalview.schemes.UserColourScheme()
1007                     .createColourFromName(name.getText());
1008           }
1009           oldcol = fcol = col;
1010           updateColourButton(bigPanel, colour,col);
1011         }
1012       });
1013
1014       panel.add(tmp);
1015     }
1016     // ////////
1017     // ////////////////////////////////////
1018
1019     tmp = new JPanel();
1020     panel.add(tmp);
1021     tmp.add(new JLabel("Name: ", JLabel.RIGHT));
1022     tmp.add(name);
1023
1024     tmp = new JPanel();
1025     panel.add(tmp);
1026     tmp.add(new JLabel("Group: ", JLabel.RIGHT));
1027     tmp.add(source);
1028
1029     tmp = new JPanel();
1030     panel.add(tmp);
1031     tmp.add(new JLabel("Colour: ", JLabel.RIGHT));
1032     tmp.add(colour);
1033     colour.setPreferredSize(new Dimension(150, 15));
1034
1035     bigPanel.add(panel, BorderLayout.NORTH);
1036
1037     panel = new JPanel();
1038     panel.add(new JLabel("Description: ", JLabel.RIGHT));
1039     description.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
1040     description.setLineWrap(true);
1041     panel.add(new JScrollPane(description));
1042
1043     if (!newFeatures)
1044     {
1045       bigPanel.add(panel, BorderLayout.SOUTH);
1046
1047       panel = new JPanel();
1048       panel.add(new JLabel(" Start:", JLabel.RIGHT));
1049       panel.add(start);
1050       panel.add(new JLabel("  End:", JLabel.RIGHT));
1051       panel.add(end);
1052       bigPanel.add(panel, BorderLayout.CENTER);
1053     }
1054     else
1055     {
1056       bigPanel.add(panel, BorderLayout.CENTER);
1057     }
1058
1059     if (lastFeatureAdded == null)
1060     {
1061       if (features[0].type != null)
1062       {
1063         lastFeatureAdded = features[0].type;
1064       }
1065       else
1066       {
1067         lastFeatureAdded = "feature_1";
1068       }
1069     }
1070
1071     if (lastFeatureGroupAdded == null)
1072     {
1073       if (features[0].featureGroup != null)
1074       {
1075         lastFeatureGroupAdded = features[0].featureGroup;
1076       }
1077       else
1078       {
1079         lastFeatureGroupAdded = "Jalview";
1080       }
1081     }
1082
1083     if (newFeatures)
1084     {
1085       name.setText(lastFeatureAdded);
1086       source.setText(lastFeatureGroupAdded);
1087     }
1088     else
1089     {
1090       name.setText(features[0].getType());
1091       source.setText(features[0].getFeatureGroup());
1092     }
1093
1094     start.setValue(new Integer(features[0].getBegin()));
1095     end.setValue(new Integer(features[0].getEnd()));
1096     description.setText(features[0].getDescription());
1097     updateColourButton(bigPanel, colour, (oldcol = fcol = getFeatureStyle(name.getText())));
1098     Object[] options;
1099     if (!newFeatures)
1100     {
1101       options = new Object[]
1102       { "Amend", "Delete", "Cancel" };
1103     }
1104     else
1105     {
1106       options = new Object[]
1107       { "OK", "Cancel" };
1108     }
1109
1110     String title = newFeatures ? "Create New Sequence Feature(s)"
1111             : "Amend/Delete Features for " + sequences[0].getName();
1112
1113     int reply = JOptionPane.showInternalOptionDialog(Desktop.desktop,
1114             bigPanel, title, JOptionPane.YES_NO_CANCEL_OPTION,
1115             JOptionPane.QUESTION_MESSAGE, null, options, "OK");
1116
1117     jalview.io.FeaturesFile ffile = new jalview.io.FeaturesFile();
1118
1119     if (reply == JOptionPane.OK_OPTION && name.getText().length() > 0)
1120     {
1121       // This ensures that the last sequence
1122       // is refreshed and new features are rendered
1123       lastSeq = null;
1124       lastFeatureAdded = name.getText().trim();
1125       lastFeatureGroupAdded = source.getText().trim();
1126       lastDescriptionAdded = description.getText().replaceAll("\n", " ");
1127
1128       if (lastFeatureGroupAdded.length() < 1)
1129         lastFeatureGroupAdded = null;
1130     }
1131
1132     if (!newFeatures)
1133     {
1134       SequenceFeature sf = features[featureIndex];
1135
1136       if (reply == JOptionPane.NO_OPTION)
1137       {
1138         sequences[0].getDatasetSequence().deleteFeature(sf);
1139       }
1140       else if (reply == JOptionPane.YES_OPTION)
1141       {
1142         sf.type = lastFeatureAdded;
1143         sf.featureGroup = lastFeatureGroupAdded;
1144         sf.description = lastDescriptionAdded;
1145
1146         setColour(sf.type, fcol);
1147         av.featuresDisplayed.put(sf.type, getColour(sf.type));
1148
1149         try
1150         {
1151           sf.begin = ((Integer) start.getValue()).intValue();
1152           sf.end = ((Integer) end.getValue()).intValue();
1153         } catch (NumberFormatException ex)
1154         {
1155         }
1156
1157         ffile.parseDescriptionHTML(sf, false);
1158       }
1159     }
1160     else
1161     // NEW FEATURES ADDED
1162     {
1163       if (reply == JOptionPane.OK_OPTION && lastFeatureAdded.length() > 0)
1164       {
1165         for (int i = 0; i < sequences.length; i++)
1166         {
1167           features[i].type = lastFeatureAdded;
1168           if (lastFeatureGroupAdded != null)
1169             features[i].featureGroup = lastFeatureGroupAdded;
1170           features[i].description = lastDescriptionAdded;
1171           sequences[i].addSequenceFeature(features[i]);
1172           ffile.parseDescriptionHTML(features[i], false);
1173         }
1174
1175         if (av.featuresDisplayed == null)
1176         {
1177           av.featuresDisplayed = new Hashtable();
1178         }
1179
1180         if (lastFeatureGroupAdded != null)
1181         {
1182           if (featureGroups == null)
1183             featureGroups = new Hashtable();
1184           featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
1185         }
1186         setColour(lastFeatureAdded, fcol);
1187         av.featuresDisplayed.put(lastFeatureAdded, getColour(lastFeatureAdded));
1188
1189         findAllFeatures(false);
1190
1191         ap.paintAlignment(true);
1192
1193         return true;
1194       }
1195       else
1196       {
1197         return false;
1198       }
1199     }
1200
1201     ap.paintAlignment(true);
1202
1203     return true;
1204   }
1205
1206   /**
1207    * update the amend feature button dependent on the given style
1208    * @param bigPanel 
1209    * @param col
1210    * @param col2 
1211    */
1212   protected void updateColourButton(JPanel bigPanel, JLabel colour, Object col2)
1213   {
1214     colour.removeAll();
1215     colour.setIcon(null);
1216     colour.setToolTipText(null);
1217     colour.setText("");
1218     if (col2 instanceof Color) {
1219       colour.setBackground((Color)col2);
1220     } else {
1221       colour.setBackground(bigPanel.getBackground());
1222       colour.setForeground(bigPanel.getForeground());
1223       FeatureSettings.renderGraduatedColor(colour, (GraduatedColor) col2);
1224       //colour.setForeground(colour.getBackground());
1225     }
1226   }
1227
1228   public void setColour(String featureType, Object col)
1229   {
1230     // overwrite
1231 //    Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null; 
1232 //    Object c = featureColours.get(featureType);
1233 //    if (c == null || c instanceof Color || (c instanceof GraduatedColor && !((GraduatedColor)c).getMaxColor().equals(_col)))
1234     {
1235       featureColours.put(featureType, col);
1236     }
1237   }
1238
1239   public void setTransparency(float value)
1240   {
1241     transparency = value;
1242   }
1243
1244   public float getTransparency()
1245   {
1246     return transparency;
1247   }
1248
1249   /**
1250    * Replace current ordering with new ordering
1251    * 
1252    * @param data {
1253    *                String(Type), Colour(Type), Boolean(Displayed) }
1254    */
1255   public void setFeaturePriority(Object[][] data)
1256   {
1257     setFeaturePriority(data, true);
1258   }
1259
1260   /**
1261    * 
1262    * @param data {
1263    *                String(Type), Colour(Type), Boolean(Displayed) }
1264    * @param visibleNew
1265    *                when true current featureDisplay list will be cleared
1266    */
1267   public void setFeaturePriority(Object[][] data, boolean visibleNew)
1268   {
1269     if (visibleNew)
1270     {
1271       if (av.featuresDisplayed != null)
1272       {
1273         av.featuresDisplayed.clear();
1274       }
1275       else
1276       {
1277         av.featuresDisplayed = new Hashtable();
1278       }
1279     }
1280     if (data == null)
1281     {
1282       return;
1283     }
1284
1285     // The feature table will display high priority
1286     // features at the top, but theses are the ones
1287     // we need to render last, so invert the data
1288     renderOrder = new String[data.length];
1289
1290     if (data.length > 0)
1291     {
1292       for (int i = 0; i < data.length; i++)
1293       {
1294         String type = data[i][0].toString();
1295         setColour(type, data[i][1]); // todo : typesafety - feature color interface object
1296         if (((Boolean) data[i][2]).booleanValue())
1297         {
1298           av.featuresDisplayed.put(type, new Integer(getColour(type)
1299                   .getRGB()));
1300         }
1301
1302         renderOrder[data.length - i - 1] = type;
1303       }
1304     }
1305
1306   }
1307
1308   Hashtable featureOrder = null;
1309
1310   /**
1311    * analogous to colour - store a normalized ordering for all feature types in
1312    * this rendering context.
1313    * 
1314    * @param type
1315    *                Feature type string
1316    * @param position
1317    *                normalized priority - 0 means always appears on top, 1 means
1318    *                always last.
1319    */
1320   public float setOrder(String type, float position)
1321   {
1322     if (featureOrder == null)
1323     {
1324       featureOrder = new Hashtable();
1325     }
1326     featureOrder.put(type, new Float(position));
1327     return position;
1328   }
1329
1330   /**
1331    * get the global priority (0 (top) to 1 (bottom))
1332    * 
1333    * @param type
1334    * @return [0,1] or -1 for a type without a priority
1335    */
1336   public float getOrder(String type)
1337   {
1338     if (featureOrder != null)
1339     {
1340       if (featureOrder.containsKey(type))
1341       {
1342         return ((Float) featureOrder.get(type)).floatValue();
1343       }
1344     }
1345     return -1;
1346   }
1347
1348   /**
1349    * @param listener
1350    * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
1351    */
1352   public void addPropertyChangeListener(PropertyChangeListener listener)
1353   {
1354     changeSupport.addPropertyChangeListener(listener);
1355   }
1356
1357   /**
1358    * @param listener
1359    * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
1360    */
1361   public void removePropertyChangeListener(PropertyChangeListener listener)
1362   {
1363     changeSupport.removePropertyChangeListener(listener);
1364   }
1365 }