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