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