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