update author list in license for (JAL-826)
[jalview.git] / src / jalview / appletgui / FeatureRenderer.java
index 551dea7..244b650 100755 (executable)
-\r
-/*\r
- * Jalview - A Sequence Alignment Editor and Viewer\r
- * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
- *\r
- * This program is free software; you can redistribute it and/or\r
- * modify it under the terms of the GNU General Public License\r
- * as published by the Free Software Foundation; either version 2\r
- * of the License, or (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program; if not, write to the Free Software\r
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
- */\r
-package jalview.appletgui;\r
-\r
-import jalview.datamodel.*;\r
-\r
-import java.awt.*;\r
-\r
-import java.util.*;\r
-\r
-/**\r
- * DOCUMENT ME!\r
- *\r
- * @author $author$\r
- * @version $Revision$\r
- */\r
-public class FeatureRenderer\r
-{\r
-    AlignViewport av;\r
-\r
-    Hashtable featureColours = new Hashtable();\r
-\r
-    // A higher level for grouping features of a\r
-    // particular type\r
-    Hashtable featureGroups = null;\r
-\r
-    // Holds web links for feature groups and feature types\r
-    // in the form label|link\r
-    Hashtable featureLinks = null;\r
-\r
-\r
-    // This is actually an Integer held in the hashtable,\r
-    // Retrieved using the key feature type\r
-    Object currentColour;\r
-\r
-    String [] renderOrder;\r
-\r
-    FontMetrics fm;\r
-    int charOffset;\r
-\r
-    float transparency = 1f;\r
-\r
-    TransparencySetter transparencySetter = null;\r
-\r
-    /**\r
-     * Creates a new FeatureRenderer object.\r
-     *\r
-     * @param av DOCUMENT ME!\r
-     */\r
-    public FeatureRenderer(AlignViewport av)\r
-    {\r
-        this.av = av;\r
-\r
-        if(!System.getProperty("java.version").startsWith("1.1"))\r
-             transparencySetter = new TransparencySetter();\r
-    }\r
-\r
-\r
-    public void transferSettings(FeatureRenderer fr)\r
-    {\r
-      renderOrder = fr.renderOrder;\r
-      featureGroups = fr.featureGroups;\r
-      featureColours = fr.featureColours;\r
-    }\r
-\r
-\r
-    public Color findFeatureColour(Color initialCol, SequenceI seq, int i)\r
-    {\r
-      overview = true;\r
-      if(!av.showSequenceFeatures)\r
-        return initialCol;\r
-\r
-        lastSequence = seq;\r
-        sequenceFeatures = lastSequence.getSequenceFeatures();\r
-        if(sequenceFeatures==null)\r
-          return initialCol;\r
-\r
-        sfSize = sequenceFeatures.length;\r
-\r
-      if(jalview.util.Comparison.isGap(lastSequence.getCharAt(i)))\r
-        return Color.white;\r
-\r
-      currentColour = null;\r
-\r
-      drawSequence(null, lastSequence, lastSequence.findPosition(i), -1,-1, -1, -1);\r
-\r
-      if(currentColour==null)\r
-        return initialCol;\r
-\r
-      return new Color( ((Integer)currentColour).intValue() );\r
-    }\r
-\r
-    /**\r
-     * This is used by the Molecule Viewer to get the accurate colour\r
-     * of the rendered sequence\r
-     */\r
-    boolean overview = false;\r
-\r
-    int white = Color.white.getRGB();\r
-    public int findFeatureColour(int initialCol, int seqIndex, int column)\r
-    {\r
-      if(!av.showSequenceFeatures)\r
-        return initialCol;\r
-\r
-      if(seqIndex!=lastSequenceIndex)\r
-      {\r
-        lastSequence = av.alignment.getSequenceAt(seqIndex);\r
-        lastSequenceIndex = seqIndex;\r
-        sequenceFeatures = lastSequence.getSequenceFeatures();\r
-        if(sequenceFeatures==null)\r
-          return initialCol;\r
-\r
-        sfSize = sequenceFeatures.length;\r
-      }\r
-\r
-\r
-      if(jalview.util.Comparison.isGap(lastSequence.getCharAt(column)))\r
-        return Color.white.getRGB();\r
-\r
-      currentColour = null;\r
-\r
-      drawSequence(null, lastSequence, lastSequence.findPosition(column), -1,-1, -1, -1);\r
-\r
-      if(currentColour==null)\r
-        return initialCol;\r
-\r
-      return  ((Integer)currentColour).intValue();\r
-    }\r
-\r
-\r
-    /**\r
-     * DOCUMENT ME!\r
-     *\r
-     * @param g DOCUMENT ME!\r
-     * @param seq DOCUMENT ME!\r
-     * @param sg DOCUMENT ME!\r
-     * @param start DOCUMENT ME!\r
-     * @param end DOCUMENT ME!\r
-     * @param x1 DOCUMENT ME!\r
-     * @param y1 DOCUMENT ME!\r
-     * @param width DOCUMENT ME!\r
-     * @param height DOCUMENT ME!\r
-     */\r
-   // String type;\r
-   // SequenceFeature sf;\r
-    int lastSequenceIndex=-1;\r
-    SequenceI lastSequence;\r
-    SequenceFeature [] sequenceFeatures;\r
-    int sfSize, sfindex, spos, epos;\r
-\r
-    public void drawSequence(Graphics g, SequenceI seq,\r
-                             int start, int end, int y1, int width, int height)\r
-    {\r
-      if (   seq.getSequenceFeatures() == null\r
-          || seq.getSequenceFeatures().length==0)\r
-        return;\r
-\r
-      if(transparencySetter!=null && g!=null)\r
-      {\r
-        transparencySetter.setTransparency(g, transparency);\r
-      }\r
-\r
-      if (av.featuresDisplayed == null || renderOrder==null)\r
-       {\r
-         findAllFeatures();\r
-         if(av.featuresDisplayed.size()<1)\r
-           return;\r
-\r
-         sequenceFeatures = seq.getSequenceFeatures();\r
-         sfSize = sequenceFeatures.length;\r
-       }\r
-       if(lastSequence==null || seq!=lastSequence)\r
-      {\r
-        lastSequence = seq;\r
-        sequenceFeatures = seq.getSequenceFeatures();\r
-        sfSize = sequenceFeatures.length;\r
-      }\r
-      if(!overview)\r
-      {\r
-        spos = lastSequence.findPosition(start);\r
-        epos = lastSequence.findPosition(end);\r
-        if(g!=null)\r
-          fm = g.getFontMetrics();\r
-      }\r
-      String type;\r
-      for(int renderIndex=0; renderIndex<renderOrder.length; renderIndex++)\r
-       {\r
-        type =  renderOrder[renderIndex];\r
-        if(!av.featuresDisplayed.containsKey(type))\r
-          continue;\r
-\r
-        // loop through all features in sequence to find\r
-        // current feature to render\r
-        for (sfindex = 0; sfindex < sfSize; sfindex++)\r
-        {\r
-          if (!sequenceFeatures[sfindex].type.equals(type))\r
-            continue;\r
-\r
-          if (featureGroups != null\r
-              && sequenceFeatures[sfindex].featureGroup != null\r
-              &&\r
-              featureGroups.containsKey(sequenceFeatures[sfindex].featureGroup)\r
-              &&\r
-              ! ( (Boolean) featureGroups.get(sequenceFeatures[sfindex].featureGroup)).\r
-              booleanValue())\r
-          {\r
-            continue;\r
-          }\r
-\r
-          if (!overview && (sequenceFeatures[sfindex].getBegin() > epos\r
-                            || sequenceFeatures[sfindex].getEnd() < spos))\r
-            continue;\r
-\r
-          if (overview)\r
-          {\r
-            if (sequenceFeatures[sfindex].begin <= start &&\r
-                sequenceFeatures[sfindex].end >= start)\r
-            {\r
-              currentColour = av.featuresDisplayed.get(sequenceFeatures[sfindex].\r
-                  type);\r
-            }\r
-\r
-          }\r
-          else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))\r
-          {\r
-\r
-            renderFeature(g, seq,\r
-                          seq.findIndex(sequenceFeatures[sfindex].begin) - 1,\r
-                          seq.findIndex(sequenceFeatures[sfindex].begin) - 1,\r
-                          new Color( ( (Integer) av.featuresDisplayed.get(\r
-                sequenceFeatures[sfindex].type)).intValue()),\r
-                          start, end, y1, width, height);\r
-            renderFeature(g, seq,\r
-                          seq.findIndex(sequenceFeatures[sfindex].end) - 1,\r
-                          seq.findIndex(sequenceFeatures[sfindex].end) - 1,\r
-                          new Color( ( (Integer) av.featuresDisplayed.get(\r
-                sequenceFeatures[sfindex].type)).intValue()),\r
-                          start, end, y1, width, height);\r
-\r
-          }\r
-          else\r
-            renderFeature(g, seq,\r
-                          seq.findIndex(sequenceFeatures[sfindex].begin) - 1,\r
-                          seq.findIndex(sequenceFeatures[sfindex].end) - 1,\r
-                          getColour(sequenceFeatures[sfindex].type),\r
-                          start, end, y1, width, height);\r
-\r
-        }\r
-      }\r
-\r
-      if(transparencySetter!=null && g!=null)\r
-      {\r
-        transparencySetter.setTransparency(g, 1.0f);\r
-      }\r
-    }\r
-\r
-\r
-    char s;\r
-    int i;\r
-    void renderFeature(Graphics g, SequenceI seq,\r
-                       int fstart, int fend, Color featureColour, int start, int end,  int y1, int width, int height)\r
-    {\r
-\r
-      if (((fstart <= end) && (fend >= start)))\r
-      {\r
-        if (fstart < start)\r
-        { // fix for if the feature we have starts before the sequence start,\r
-          fstart = start; // but the feature end is still valid!!\r
-        }\r
-\r
-        if (fend >= end)\r
-        {\r
-          fend = end;\r
-        }\r
-\r
-          for (i = fstart; i <= fend; i++)\r
-          {\r
-            s = seq.getSequence().charAt(i);\r
-\r
-            if (jalview.util.Comparison.isGap(s))\r
-            {\r
-              continue;\r
-            }\r
-\r
-            g.setColor(featureColour);\r
-\r
-            g.fillRect( (i - start) * width, y1, width, height);\r
-\r
-            if(!av.validCharWidth)\r
-              continue;\r
-\r
-            g.setColor(Color.white);\r
-            charOffset = (width - fm.charWidth(s)) / 2;\r
-            g.drawString(String.valueOf(s),\r
-                         charOffset + (width * (i - start)),\r
-                         (y1 + height) - height / 5); //pady = height / 5;\r
-\r
-          }\r
-      }\r
-    }\r
-\r
-    void findAllFeatures()\r
-    {\r
-      av.featuresDisplayed = new Hashtable();\r
-      Vector allfeatures = new Vector();\r
-      for (int i = 0; i < av.alignment.getHeight(); i++)\r
-      {\r
-        SequenceFeature [] features = av.alignment.getSequenceAt(i).getSequenceFeatures();\r
-\r
-        if (features == null)\r
-          continue;\r
-\r
-        int index = 0;\r
-        while (index < features.length)\r
-        {\r
-          if (!av.featuresDisplayed.containsKey(features[index].getType()))\r
-          {\r
-            av.featuresDisplayed.put(features[index].getType(),\r
-                                  new Integer( getColour(features[index].getType()).getRGB()) );\r
-            allfeatures.addElement(features[index].getType());\r
-          }\r
-          index++;\r
-        }\r
-      }\r
-\r
-      renderOrder = new String[allfeatures.size()];\r
-      Enumeration en = allfeatures.elements();\r
-      int i = allfeatures.size()-1;\r
-      while(en.hasMoreElements())\r
-      {\r
-        renderOrder[i] = en.nextElement().toString();\r
-        i--;\r
-      }\r
-    }\r
-\r
-    public Color getColour(String featureType)\r
-    {\r
-      return (Color)featureColours.get(featureType);\r
-    }\r
-\r
-    public void addNewFeature(String name, Color col)\r
-    {\r
-\r
-      setColour(name, col);\r
-      if(av.featuresDisplayed==null)\r
-        av.featuresDisplayed = new Hashtable();\r
-\r
-\r
-      av.featuresDisplayed.put(name, "NOGROUP");\r
-    }\r
-\r
-    public void setColour(String featureType, Color col)\r
-    {\r
-      featureColours.put(featureType, col);\r
-    }\r
-\r
-    public void setFeaturePriority(Object [][] data)\r
-    {\r
-        // The feature table will display high priority\r
-        // features at the top, but theses are the ones\r
-        // we need to render last, so invert the data\r
-        if(av.featuresDisplayed!=null)\r
-          av.featuresDisplayed.clear();\r
-\r
-        renderOrder = new String[data.length];\r
-\r
-        if (data.length > 0)\r
-          for (int i = 0; i < data.length; i++)\r
-          {\r
-            String type = data[i][0].toString();\r
-            setColour(type, (Color) data[i][1]);\r
-            if ( ( (Boolean) data[i][2]).booleanValue())\r
-            {\r
-              av.featuresDisplayed.put(type, new Integer(getColour(type).getRGB()));\r
-            }\r
-\r
-            renderOrder[data.length - i - 1] = type;\r
-          }\r
-    }\r
-}\r
-\r
-  class TransparencySetter\r
-  {\r
-    void setTransparency(Graphics g, float value)\r
-    {\r
-  //    Graphics2D g2 = (Graphics2D) g;\r
-   //   g2.setComposite(\r
-    //      AlphaComposite.getInstance(\r
-    //          AlphaComposite.SRC_OVER, value));\r
-    }\r
-  }\r
-\r
-\r
-\r
-\r
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
+ * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ * 
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package jalview.appletgui;
+
+import java.util.*;
+
+import java.awt.*;
+
+import java.awt.event.*;
+
+import jalview.appletgui.FeatureSettings.MyCheckbox;
+import jalview.datamodel.*;
+import jalview.schemes.AnnotationColourGradient;
+import jalview.schemes.GraduatedColor;
+
+/**
+ * DOCUMENT ME!
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class FeatureRenderer implements jalview.api.FeatureRenderer
+{
+  AlignViewport av;
+
+  Hashtable featureColours = new Hashtable();
+
+  // A higher level for grouping features of a
+  // particular type
+  Hashtable featureGroups = null;
+
+  // Holds web links for feature groups and feature types
+  // in the form label|link
+  Hashtable featureLinks = null;
+
+  // This is actually an Integer held in the hashtable,
+  // Retrieved using the key feature type
+  Object currentColour;
+
+  String[] renderOrder;
+
+  FontMetrics fm;
+
+  int charOffset;
+
+  float transparency = 1f;
+
+  TransparencySetter transparencySetter = null;
+
+  /**
+   * Creates a new FeatureRenderer object.
+   * 
+   * @param av
+   *          DOCUMENT ME!
+   */
+  public FeatureRenderer(AlignViewport av)
+  {
+    this.av = av;
+
+    if (!System.getProperty("java.version").startsWith("1.1"))
+    {
+      transparencySetter = new TransparencySetter();
+    }
+  }
+
+  public void transferSettings(FeatureRenderer fr)
+  {
+    renderOrder = fr.renderOrder;
+    featureGroups = fr.featureGroups;
+    featureColours = fr.featureColours;
+    transparency = fr.transparency;
+    if (av!=null && fr.av!=null && fr.av!=av)
+    {
+      if (fr.av.featuresDisplayed!=null)
+      {
+        if (av.featuresDisplayed==null)
+        {
+          av.featuresDisplayed = new Hashtable();
+        } else {
+          av.featuresDisplayed.clear();
+        }
+        Enumeration en=fr.av.featuresDisplayed.keys();
+        while (en.hasMoreElements())
+        {
+          av.featuresDisplayed.put(en.nextElement(), Boolean.TRUE);
+        }
+      }
+    }
+  }
+
+  static String lastFeatureAdded;
+
+  static String lastFeatureGroupAdded;
+
+  static String lastDescriptionAdded;
+
+  int featureIndex = 0;
+
+  boolean deleteFeature = false;
+
+  FeatureColourPanel colourPanel;
+
+  class FeatureColourPanel extends Panel
+  {
+    String label = "";
+
+    private Color maxCol;
+
+    private boolean isColourByLabel, isGcol;
+
+    /**
+     * render a feature style in the amend feature dialog box
+     */
+    public void updateColor(Object newcol)
+    {
+
+      Color bg, col = null;
+      GraduatedColor gcol = null;
+      String vlabel = "";
+      if (newcol instanceof Color)
+      {
+        isGcol = false;
+        col = (Color) newcol;
+        gcol = null;
+      }
+      else if (newcol instanceof GraduatedColor)
+      {
+        isGcol = true;
+        gcol = (GraduatedColor) newcol;
+        col = null;
+      }
+      else
+      {
+        throw new Error("Invalid color for MyCheckBox");
+      }
+      if (col != null)
+      {
+        setBackground(bg = col);
+      }
+      else
+      {
+        if (gcol.getThreshType() != AnnotationColourGradient.NO_THRESHOLD)
+        {
+          vlabel += " "
+                  + ((gcol.getThreshType() == AnnotationColourGradient.ABOVE_THRESHOLD) ? "(>)"
+                          : "(<)");
+        }
+        if (isColourByLabel = gcol.isColourByLabel())
+        {
+          setBackground(bg = Color.white);
+          vlabel += " (by Label)";
+        }
+        else
+        {
+          setBackground(bg = gcol.getMinColor());
+          maxCol = gcol.getMaxColor();
+        }
+      }
+      label = vlabel;
+      setBackground(bg);
+      repaint();
+    }
+
+    FeatureColourPanel()
+    {
+      super(null);
+    }
+
+    public void paint(Graphics g)
+    {
+      Dimension d = getSize();
+      if (isGcol)
+      {
+        if (isColourByLabel)
+        {
+          g.setColor(Color.white);
+          g.fillRect(d.width / 2, 0, d.width / 2, d.height);
+          g.setColor(Color.black);
+          Font f = new Font("Verdana", Font.PLAIN, 10);
+          g.setFont(f);
+          g.drawString("Label", 0, 0);
+        }
+        else
+        {
+          g.setColor(maxCol);
+          g.fillRect(d.width / 2, 0, d.width / 2, d.height);
+
+        }
+      }
+    }
+
+  }
+
+  boolean amendFeatures(final SequenceI[] sequences,
+          final SequenceFeature[] features, boolean newFeatures,
+          final AlignmentPanel ap)
+  {
+    Panel bigPanel = new Panel(new BorderLayout());
+    final TextField name = new TextField(16);
+    final TextField source = new TextField(16);
+    final TextArea description = new TextArea(3, 35);
+    final TextField start = new TextField(8);
+    final TextField end = new TextField(8);
+    final Choice overlaps;
+    Button deleteButton = new Button("Delete");
+    deleteFeature = false;
+
+    colourPanel = new FeatureColourPanel();
+    colourPanel.setSize(110, 15);
+    final FeatureRenderer fr = this;
+
+    Panel panel = new Panel(new GridLayout(3, 1));
+
+    featureIndex = 0; // feature to be amended.
+    Panel tmp;
+
+    // /////////////////////////////////////
+    // /MULTIPLE FEATURES AT SELECTED RESIDUE
+    if (!newFeatures && features.length > 1)
+    {
+      panel = new Panel(new GridLayout(4, 1));
+      tmp = new Panel();
+      tmp.add(new Label("Select Feature: "));
+      overlaps = new Choice();
+      for (int i = 0; i < features.length; i++)
+      {
+        String item = features[i].getType() + "/" + features[i].getBegin()
+                + "-" + features[i].getEnd();
+
+        if (features[i].getFeatureGroup() != null)
+          item += " (" + features[i].getFeatureGroup() + ")";
+
+        overlaps.addItem(item);
+      }
+
+      tmp.add(overlaps);
+
+      overlaps.addItemListener(new java.awt.event.ItemListener()
+      {
+        public void itemStateChanged(java.awt.event.ItemEvent e)
+        {
+          int index = overlaps.getSelectedIndex();
+          if (index != -1)
+          {
+            featureIndex = index;
+            name.setText(features[index].getType());
+            description.setText(features[index].getDescription());
+            source.setText(features[index].getFeatureGroup());
+            start.setText(features[index].getBegin() + "");
+            end.setText(features[index].getEnd() + "");
+
+            SearchResults highlight = new SearchResults();
+            highlight.addResult(sequences[0], features[index].getBegin(),
+                    features[index].getEnd());
+
+            ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
+
+          }
+          Object col = getFeatureStyle(name.getText());
+          if (col == null)
+          {
+            col = new jalview.schemes.UserColourScheme()
+                    .createColourFromName(name.getText());
+          }
+
+          colourPanel.updateColor(col);
+        }
+      });
+
+      panel.add(tmp);
+    }
+    // ////////
+    // ////////////////////////////////////
+
+    tmp = new Panel();
+    panel.add(tmp);
+    tmp.add(new Label("Name: ", Label.RIGHT));
+    tmp.add(name);
+
+    tmp = new Panel();
+    panel.add(tmp);
+    tmp.add(new Label("Group: ", Label.RIGHT));
+    tmp.add(source);
+
+    tmp = new Panel();
+    panel.add(tmp);
+    tmp.add(new Label("Colour: ", Label.RIGHT));
+    tmp.add(colourPanel);
+
+    bigPanel.add(panel, BorderLayout.NORTH);
+
+    panel = new Panel();
+    panel.add(new Label("Description: ", Label.RIGHT));
+    panel.add(new ScrollPane().add(description));
+
+    if (!newFeatures)
+    {
+      bigPanel.add(panel, BorderLayout.SOUTH);
+
+      panel = new Panel();
+      panel.add(new Label(" Start:", Label.RIGHT));
+      panel.add(start);
+      panel.add(new Label("  End:", Label.RIGHT));
+      panel.add(end);
+      bigPanel.add(panel, BorderLayout.CENTER);
+    }
+    else
+    {
+      bigPanel.add(panel, BorderLayout.CENTER);
+    }
+
+    if (lastFeatureAdded == null)
+    {
+      if (features[0].type != null)
+      {
+        lastFeatureAdded = features[0].type;
+      }
+      else
+      {
+        lastFeatureAdded = "feature_1";
+      }
+    }
+
+    if (lastFeatureGroupAdded == null)
+    {
+      if (features[0].featureGroup != null)
+      {
+        lastFeatureGroupAdded = features[0].featureGroup;
+      }
+      else
+      {
+        lastFeatureAdded = "Jalview";
+      }
+    }
+
+    String title = newFeatures ? "Create New Sequence Feature(s)"
+            : "Amend/Delete Features for " + sequences[0].getName();
+
+    final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385,
+            240);
+
+    dialog.setMainPanel(bigPanel);
+
+    if (newFeatures)
+    {
+      name.setText(lastFeatureAdded);
+      source.setText(lastFeatureGroupAdded);
+    }
+    else
+    {
+      dialog.ok.setLabel("Amend");
+      dialog.buttonPanel.add(deleteButton, 1);
+      deleteButton.addActionListener(new ActionListener()
+      {
+        public void actionPerformed(ActionEvent evt)
+        {
+          deleteFeature = true;
+          dialog.setVisible(false);
+        }
+      });
+      name.setText(features[0].getType());
+      source.setText(features[0].getFeatureGroup());
+    }
+
+    start.setText(features[0].getBegin() + "");
+    end.setText(features[0].getEnd() + "");
+    description.setText(features[0].getDescription());
+    Color col = getColour(name.getText());
+    if (col == null)
+    {
+      col = new jalview.schemes.UserColourScheme()
+              .createColourFromName(name.getText());
+    }
+    Object fcol = getFeatureStyle(name.getText());
+    // simply display the feature color in a box
+    colourPanel.updateColor(fcol);
+    dialog.setResizable(true);
+    // TODO: render the graduated color in the box.
+    colourPanel.addMouseListener(new java.awt.event.MouseAdapter()
+    {
+      public void mousePressed(java.awt.event.MouseEvent evt)
+      {
+        if (!colourPanel.isGcol)
+        {
+          new UserDefinedColours(fr, ap.alignFrame);
+        }
+        else
+        {
+          FeatureColourChooser fcc = new FeatureColourChooser(
+                  ap.alignFrame, name.getText());
+          dialog.transferFocus();
+        }
+      }
+    });
+    dialog.setVisible(true);
+
+    jalview.io.FeaturesFile ffile = new jalview.io.FeaturesFile();
+
+    if (dialog.accept)
+    {
+      // This ensures that the last sequence
+      // is refreshed and new features are rendered
+      lastSeq = null;
+      lastFeatureAdded = name.getText().trim();
+      lastFeatureGroupAdded = source.getText().trim();
+      lastDescriptionAdded = description.getText().replace('\n', ' ');
+    }
+
+    if (lastFeatureGroupAdded != null && lastFeatureGroupAdded.length() < 1)
+      lastFeatureGroupAdded = null;
+
+    if (!newFeatures)
+    {
+
+      SequenceFeature sf = features[featureIndex];
+      if (dialog.accept)
+      {
+        sf.type = lastFeatureAdded;
+        sf.featureGroup = lastFeatureGroupAdded;
+        sf.description = lastDescriptionAdded;
+        if (!colourPanel.isGcol)
+        {
+          // update colour - otherwise its already done.
+          setColour(sf.type, colourPanel.getBackground());
+        }
+        try
+        {
+          sf.begin = Integer.parseInt(start.getText());
+          sf.end = Integer.parseInt(end.getText());
+        } catch (NumberFormatException ex)
+        {
+        }
+
+        ffile.parseDescriptionHTML(sf, false);
+      }
+      if (deleteFeature)
+      {
+        sequences[0].deleteFeature(sf);
+      }
+
+    }
+    else
+    {
+      if (dialog.accept && name.getText().length() > 0)
+      {
+        for (int i = 0; i < sequences.length; i++)
+        {
+          features[i].type = lastFeatureAdded;
+          features[i].featureGroup = lastFeatureGroupAdded;
+          features[i].description = lastDescriptionAdded;
+          sequences[i].addSequenceFeature(features[i]);
+          ffile.parseDescriptionHTML(features[i], false);
+        }
+
+        if (av.featuresDisplayed == null)
+        {
+          av.featuresDisplayed = new Hashtable();
+        }
+
+        if (featureGroups == null)
+        {
+          featureGroups = new Hashtable();
+        }
+
+        col = colourPanel.getBackground();
+        // setColour(lastFeatureAdded, fcol);
+
+        if (lastFeatureGroupAdded != null)
+        {
+          featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
+        }
+        if (fcol instanceof Color)
+        {
+          setColour(lastFeatureAdded, fcol);
+        }
+        av.featuresDisplayed.put(lastFeatureAdded,
+                getFeatureStyle(lastFeatureAdded));
+
+        findAllFeatures();
+
+        String[] tro = new String[renderOrder.length];
+        tro[0] = renderOrder[renderOrder.length - 1];
+        System.arraycopy(renderOrder, 0, tro, 1, renderOrder.length - 1);
+        renderOrder = tro;
+      }
+      else
+      {
+        // no update to the alignment
+        return false;
+      }
+    }
+    // refresh the alignment and the feature settings dialog
+    if (av.featureSettings != null)
+    {
+      av.featureSettings.refreshTable();
+    }
+    // findAllFeatures();
+
+    ap.paintAlignment(true);
+
+    return true;
+  }
+
+  public Color findFeatureColour(Color initialCol, SequenceI seq, int i)
+  {
+    overview = true;
+    if (!av.showSequenceFeatures)
+    {
+      return initialCol;
+    }
+
+    lastSeq = seq;
+    sequenceFeatures = lastSeq.getSequenceFeatures();
+    if (sequenceFeatures == null)
+    {
+      return initialCol;
+    }
+
+    sfSize = sequenceFeatures.length;
+
+    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(i)))
+    {
+      return Color.white;
+    }
+
+    currentColour = null;
+
+    drawSequence(null, lastSeq, lastSeq.findPosition(i), -1, -1);
+
+    if (currentColour == null)
+    {
+      return initialCol;
+    }
+
+    return new Color(((Integer) currentColour).intValue());
+  }
+
+  /**
+   * This is used by the Molecule Viewer to get the accurate colour of the
+   * rendered sequence
+   */
+  boolean overview = false;
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   * @param seq
+   *          DOCUMENT ME!
+   * @param sg
+   *          DOCUMENT ME!
+   * @param start
+   *          DOCUMENT ME!
+   * @param end
+   *          DOCUMENT ME!
+   * @param x1
+   *          DOCUMENT ME!
+   * @param y1
+   *          DOCUMENT ME!
+   * @param width
+   *          DOCUMENT ME!
+   * @param height
+   *          DOCUMENT ME!
+   */
+  // String type;
+  // SequenceFeature sf;
+  SequenceI lastSeq;
+
+  SequenceFeature[] sequenceFeatures;
+
+  int sfSize, sfindex, spos, epos;
+
+  synchronized public void drawSequence(Graphics g, SequenceI seq,
+          int start, int end, int y1)
+  {
+    if (seq.getSequenceFeatures() == null
+            || seq.getSequenceFeatures().length == 0)
+    {
+      return;
+    }
+
+    if (transparencySetter != null && g != null)
+    {
+      transparencySetter.setTransparency(g, transparency);
+    }
+
+    if (lastSeq == null || seq != lastSeq
+            || sequenceFeatures != seq.getSequenceFeatures())
+    {
+      lastSeq = seq;
+      sequenceFeatures = seq.getSequenceFeatures();
+      sfSize = sequenceFeatures.length;
+    }
+
+    if (av.featuresDisplayed == null || renderOrder == null)
+    {
+      findAllFeatures();
+      if (av.featuresDisplayed.size() < 1)
+      {
+        return;
+      }
+
+      sequenceFeatures = seq.getSequenceFeatures();
+      sfSize = sequenceFeatures.length;
+    }
+    if (!overview)
+    {
+      spos = lastSeq.findPosition(start);
+      epos = lastSeq.findPosition(end);
+      if (g != null)
+      {
+        fm = g.getFontMetrics();
+      }
+    }
+    String type;
+    for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
+    {
+      type = renderOrder[renderIndex];
+      if (!av.featuresDisplayed.containsKey(type))
+      {
+        continue;
+      }
+
+      // loop through all features in sequence to find
+      // current feature to render
+      for (sfindex = 0; sfindex < sfSize; sfindex++)
+      {
+        if (!sequenceFeatures[sfindex].type.equals(type))
+        {
+          continue;
+        }
+
+        if (featureGroups != null
+                && sequenceFeatures[sfindex].featureGroup != null
+                && featureGroups
+                        .containsKey(sequenceFeatures[sfindex].featureGroup)
+                && !((Boolean) featureGroups
+                        .get(sequenceFeatures[sfindex].featureGroup))
+                        .booleanValue())
+        {
+          continue;
+        }
+
+        if (!overview
+                && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
+                        .getEnd() < spos))
+        {
+          continue;
+        }
+
+        if (overview)
+        {
+          if (sequenceFeatures[sfindex].begin <= start
+                  && sequenceFeatures[sfindex].end >= start)
+          {
+            currentColour = new Integer(
+                    getColour(sequenceFeatures[sfindex]).getRGB());// av.featuresDisplayed
+            // .get(sequenceFeatures[sfindex].type);
+          }
+
+        }
+        else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
+        {
+
+          renderFeature(g, seq,
+                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                  getColour(sequenceFeatures[sfindex])
+                  // new Color(((Integer) av.featuresDisplayed
+                  // .get(sequenceFeatures[sfindex].type)).intValue())
+                  , start, end, y1);
+          renderFeature(g, seq,
+                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                  getColour(sequenceFeatures[sfindex])
+                  // new Color(((Integer) av.featuresDisplayed
+                  // .get(sequenceFeatures[sfindex].type)).intValue())
+                  , start, end, y1);
+
+        }
+        else
+        {
+          if (showFeature(sequenceFeatures[sfindex]))
+          {
+            renderFeature(g, seq,
+                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                    getColour(sequenceFeatures[sfindex]), start, end, y1);
+          }
+        }
+
+      }
+    }
+
+    if (transparencySetter != null && g != null)
+    {
+      transparencySetter.setTransparency(g, 1.0f);
+    }
+  }
+
+  char s;
+
+  int i;
+
+  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
+          Color featureColour, int start, int end, int y1)
+  {
+
+    if (((fstart <= end) && (fend >= start)))
+    {
+      if (fstart < start)
+      { // fix for if the feature we have starts before the sequence start,
+        fstart = start; // but the feature end is still valid!!
+      }
+
+      if (fend >= end)
+      {
+        fend = end;
+      }
+
+      for (i = fstart; i <= fend; i++)
+      {
+        s = seq.getCharAt(i);
+
+        if (jalview.util.Comparison.isGap(s))
+        {
+          continue;
+        }
+
+        g.setColor(featureColour);
+
+        g.fillRect((i - start) * av.charWidth, y1, av.charWidth,
+                av.charHeight);
+
+        if (!av.validCharWidth)
+        {
+          continue;
+        }
+
+        g.setColor(Color.white);
+        charOffset = (av.charWidth - fm.charWidth(s)) / 2;
+        g.drawString(String.valueOf(s), charOffset
+                + (av.charWidth * (i - start)), (y1 + av.charHeight)
+                - av.charHeight / 5); // pady = height / 5;
+
+      }
+    }
+  }
+
+  Hashtable minmax = null;
+
+  /**
+   * Called when alignment in associated view has new/modified features to
+   * discover and display.
+   * 
+   */
+  public void featuresAdded()
+  {
+    lastSeq = null;
+    findAllFeatures();
+  }
+
+  /**
+   * find all features on the alignment
+   */
+  void findAllFeatures()
+  {
+    jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
+
+    av.featuresDisplayed = new Hashtable();
+    Vector allfeatures = new Vector();
+    minmax = new Hashtable();
+
+    for (int i = 0; i < av.alignment.getHeight(); i++)
+    {
+      SequenceFeature[] features = av.alignment.getSequenceAt(i)
+              .getSequenceFeatures();
+
+      if (features == null)
+      {
+        continue;
+      }
+
+      int index = 0;
+      while (index < features.length)
+      {
+        if (features[index].begin == 0 && features[index].end == 0)
+        {
+          index++;
+          continue;
+        }
+        if (!av.featuresDisplayed.containsKey(features[index].getType()))
+        {
+          if (getColour(features[index].getType()) == null)
+          {
+            featureColours.put(features[index].getType(),
+                    ucs.createColourFromName(features[index].getType()));
+          }
+
+          av.featuresDisplayed.put(features[index].getType(), new Integer(
+                  getColour(features[index].getType()).getRGB()));
+          allfeatures.addElement(features[index].getType());
+        }
+        if (features[index].score != Float.NaN)
+        {
+          int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
+          float[][] mm = (float[][]) minmax.get(features[index].getType());
+          if (mm == null)
+          {
+            mm = new float[][]
+            { null, null };
+            minmax.put(features[index].getType(), mm);
+          }
+          if (mm[nonpos] == null)
+          {
+            mm[nonpos] = new float[]
+            { features[index].score, features[index].score };
+
+          }
+          else
+          {
+            if (mm[nonpos][0] > features[index].score)
+            {
+              mm[nonpos][0] = features[index].score;
+            }
+            if (mm[nonpos][1] < features[index].score)
+            {
+              mm[nonpos][1] = features[index].score;
+            }
+          }
+        }
+
+        index++;
+      }
+    }
+
+    renderOrder = new String[allfeatures.size()];
+    Enumeration en = allfeatures.elements();
+    int i = allfeatures.size() - 1;
+    while (en.hasMoreElements())
+    {
+      renderOrder[i] = en.nextElement().toString();
+      i--;
+    }
+  }
+
+  /**
+   * get a feature style object for the given type string. Creates a
+   * java.awt.Color for a featureType with no existing colourscheme. TODO:
+   * replace return type with object implementing standard abstract colour/style
+   * interface
+   * 
+   * @param featureType
+   * @return java.awt.Color or GraduatedColor
+   */
+  public Object getFeatureStyle(String featureType)
+  {
+    Object fc = featureColours.get(featureType);
+    if (fc == null)
+    {
+      jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
+      Color col = ucs.createColourFromName(featureType);
+      featureColours.put(featureType, fc = col);
+    }
+    return fc;
+  }
+
+  public Color getColour(String featureType)
+  {
+    Object fc = getFeatureStyle(featureType);
+
+    if (fc instanceof Color)
+    {
+      return (Color) fc;
+    }
+    else
+    {
+      if (fc instanceof GraduatedColor)
+      {
+        return ((GraduatedColor) fc).getMaxColor();
+      }
+    }
+    throw new Error("Implementation Error: Unrecognised render object "
+            + fc.getClass() + " for features of type " + featureType);
+  }
+
+  /**
+   * 
+   * @param sequenceFeature
+   * @return true if feature is visible.
+   */
+  private boolean showFeature(SequenceFeature sequenceFeature)
+  {
+    Object fc = getFeatureStyle(sequenceFeature.type);
+    if (fc instanceof GraduatedColor)
+    {
+      return ((GraduatedColor) fc).isColored(sequenceFeature);
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+  /**
+   * implement graduated colouring for features with scores
+   * 
+   * @param feature
+   * @return render colour for the given feature
+   */
+  public Color getColour(SequenceFeature feature)
+  {
+    Object fc = getFeatureStyle(feature.getType());
+    if (fc instanceof Color)
+    {
+      return (Color) fc;
+    }
+    else
+    {
+      if (fc instanceof GraduatedColor)
+      {
+        return ((GraduatedColor) fc).findColor(feature);
+      }
+    }
+    throw new Error("Implementation Error: Unrecognised render object "
+            + fc.getClass() + " for features of type " + feature.getType());
+  }
+
+  public void setColour(String featureType, Object col)
+  {
+    // overwrite
+    // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
+    // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
+    // Object c = featureColours.get(featureType);
+    // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
+    // !((GraduatedColor)c).getMaxColor().equals(_col)))
+    {
+      featureColours.put(featureType, col);
+    }
+  }
+
+  public void setFeaturePriority(Object[][] data)
+  {
+    // The feature table will display high priority
+    // features at the top, but theses are the ones
+    // we need to render last, so invert the data
+    if (av.featuresDisplayed != null)
+    {
+      av.featuresDisplayed.clear();
+    }
+
+    /*
+     * if (visibleNew) { if (av.featuresDisplayed != null) {
+     * av.featuresDisplayed.clear(); } else { av.featuresDisplayed = new
+     * Hashtable(); } } if (data == null) { return; }
+     */
+
+    renderOrder = new String[data.length];
+
+    if (data.length > 0)
+    {
+      for (int i = 0; i < data.length; i++)
+      {
+        String type = data[i][0].toString();
+        setColour(type, data[i][1]);
+        if (((Boolean) data[i][2]).booleanValue())
+        {
+          av.featuresDisplayed.put(type, new Integer(getColour(type)
+                  .getRGB()));
+        }
+
+        renderOrder[data.length - i - 1] = type;
+      }
+    }
+  }
+
+  /**
+   * @return a simple list of feature group names or null
+   */
+  public String[] getGroups()
+  {
+    buildGroupHash();
+    if (featureGroups != null)
+    {
+      String[] gps = new String[featureGroups.size()];
+      Enumeration gn = featureGroups.keys();
+      int i = 0;
+      while (gn.hasMoreElements())
+      {
+        gps[i++] = (String) gn.nextElement();
+      }
+      return gps;
+    }
+    return null;
+  }
+
+  /**
+   * get visible or invisible groups
+   * 
+   * @param visible
+   *          true to return visible groups, false to return hidden ones.
+   * @return list of groups
+   */
+  public String[] getGroups(boolean visible)
+  {
+    buildGroupHash();
+    if (featureGroups != null)
+    {
+      Vector gp = new Vector();
+
+      Enumeration gn = featureGroups.keys();
+      while (gn.hasMoreElements())
+      {
+        String nm = (String) gn.nextElement();
+        Boolean state = (Boolean) featureGroups.get(nm);
+        if (state.booleanValue() == visible)
+        {
+          gp.addElement(nm);
+        }
+      }
+      String[] gps = new String[gp.size()];
+      gp.copyInto(gps);
+
+      int i = 0;
+      while (gn.hasMoreElements())
+      {
+        gps[i++] = (String) gn.nextElement();
+      }
+      return gps;
+    }
+    return null;
+  }
+
+  /**
+   * set all feature groups in toset to be visible or invisible
+   * 
+   * @param toset
+   *          group names
+   * @param visible
+   *          the state of the named groups to set
+   */
+  public void setGroupState(String[] toset, boolean visible)
+  {
+    buildGroupHash();
+    if (toset != null && toset.length > 0 && featureGroups != null)
+    {
+      boolean rdrw = false;
+      for (int i = 0; i < toset.length; i++)
+      {
+        Object st = featureGroups.get(toset[i]);
+        if (st != null)
+        {
+          featureGroups.put(toset[i], new Boolean(visible));
+          rdrw = rdrw || (visible != ((Boolean) st).booleanValue());
+        }
+      }
+      if (rdrw)
+      {
+        if (this.av != null)
+          if (this.av.featureSettings != null)
+          {
+            av.featureSettings.rebuildGroups();
+            this.av.featureSettings.resetTable(true);
+          }
+          else
+          {
+            buildFeatureHash();
+          }
+        if (av != null)
+        {
+          av.alignmentChanged(null);
+        }
+      }
+    }
+  }
+
+  /**
+   * analyse alignment for groups and hash tables (used to be embedded in
+   * FeatureSettings.setTableData)
+   * 
+   * @return true if features are on the alignment
+   */
+  public boolean buildGroupHash()
+  {
+    boolean alignmentHasFeatures = false;
+    if (featureGroups == null)
+    {
+      featureGroups = new Hashtable();
+    }
+    Vector allFeatures = new Vector();
+    Vector allGroups = new Vector();
+    SequenceFeature[] tmpfeatures;
+    String group;
+    for (int i = 0; i < av.alignment.getHeight(); i++)
+    {
+      if (av.alignment.getSequenceAt(i).getSequenceFeatures() == null)
+      {
+        continue;
+      }
+
+      alignmentHasFeatures = true;
+
+      tmpfeatures = av.alignment.getSequenceAt(i).getSequenceFeatures();
+      int index = 0;
+      while (index < tmpfeatures.length)
+      {
+        if (tmpfeatures[index].getFeatureGroup() != null)
+        {
+          group = tmpfeatures[index].featureGroup;
+          if (!allGroups.contains(group))
+          {
+            allGroups.addElement(group);
+
+            boolean visible = true;
+            if (featureGroups.containsKey(group))
+            {
+              visible = ((Boolean) featureGroups.get(group)).booleanValue();
+            }
+            else
+            {
+              featureGroups.put(group, new Boolean(visible));
+            }
+          }
+        }
+
+        if (!allFeatures.contains(tmpfeatures[index].getType()))
+        {
+          allFeatures.addElement(tmpfeatures[index].getType());
+        }
+        index++;
+      }
+    }
+
+    return alignmentHasFeatures;
+  }
+
+  /**
+   * rebuild the featuresDisplayed and renderorder list based on the
+   * featureGroups hash and any existing display state and force a repaint if
+   * necessary
+   * 
+   * @return true if alignment has visible features
+   */
+  public boolean buildFeatureHash()
+  {
+    boolean alignmentHasFeatures = false;
+    if (featureGroups == null)
+    {
+      alignmentHasFeatures = buildGroupHash();
+    }
+    if (!alignmentHasFeatures)
+      return false;
+    Hashtable fdisp = av.featuresDisplayed;
+    Vector allFeatures = new Vector();
+    SequenceFeature[] tmpfeatures;
+    String group;
+    for (int i = 0; i < av.alignment.getHeight(); i++)
+    {
+      if (av.alignment.getSequenceAt(i).getSequenceFeatures() == null)
+      {
+        continue;
+      }
+
+      alignmentHasFeatures = true;
+
+      tmpfeatures = av.alignment.getSequenceAt(i).getSequenceFeatures();
+      int index = 0;
+      while (index < tmpfeatures.length)
+      {
+        boolean visible = true;
+        if (tmpfeatures[index].getFeatureGroup() != null)
+        {
+          group = tmpfeatures[index].featureGroup;
+          if (featureGroups.containsKey(group))
+          {
+            visible = ((Boolean) featureGroups.get(group)).booleanValue();
+          }
+        }
+
+        if (visible && !allFeatures.contains(tmpfeatures[index].getType()))
+        {
+          allFeatures.addElement(tmpfeatures[index].getType());
+        }
+        index++;
+      }
+    }
+    if (allFeatures.size() > 0)
+    {
+      String[] neworder = new String[allFeatures.size()];
+      int p = neworder.length - 1;
+      for (int i = renderOrder.length - 1; i >= 0; i--)
+      {
+        if (allFeatures.contains(renderOrder[i]))
+        {
+          neworder[p--] = renderOrder[i];
+          allFeatures.removeElement(renderOrder[i]);
+        }
+        else
+        {
+          av.featuresDisplayed.remove(renderOrder[i]);
+        }
+      }
+      for (int i = allFeatures.size() - 1; i > 0; i++)
+      {
+        Object e = allFeatures.elementAt(i);
+        if (e != null)
+        {
+          neworder[p--] = (String) e;
+          av.featuresDisplayed.put(e, getColour((String) e));
+        }
+      }
+      renderOrder = neworder;
+      return true;
+    }
+
+    return alignmentHasFeatures;
+  }
+
+  /**
+   * 
+   * @return the displayed feature type as an array of strings
+   */
+  protected String[] getDisplayedFeatureTypes()
+  {
+    String[] typ = null;
+    synchronized (renderOrder)
+    {
+      typ = new String[renderOrder.length];
+      System.arraycopy(renderOrder, 0, typ, 0, typ.length);
+      for (int i = 0; i < typ.length; i++)
+      {
+        if (av.featuresDisplayed.get(typ[i]) == null)
+        {
+          typ[i] = null;
+        }
+      }
+    }
+    return typ;
+  }
+}
+
+class TransparencySetter
+{
+  void setTransparency(Graphics g, float value)
+  {
+    Graphics2D g2 = (Graphics2D) g;
+    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+            value));
+  }
+}