Merge branch 'JAL-1483_29dev' into develop
[jalview.git] / src / jalview / renderer / seqfeatures / FeatureRenderer.java
diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java
new file mode 100644 (file)
index 0000000..909ee10
--- /dev/null
@@ -0,0 +1,435 @@
+package jalview.renderer.seqfeatures;
+
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+public class FeatureRenderer extends
+        jalview.viewmodel.seqfeatures.FeatureRendererModel
+{
+
+  FontMetrics fm;
+
+  int charOffset;
+
+  boolean offscreenRender = 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!
+   */
+  protected SequenceI lastSeq;
+
+  char s;
+
+  int i;
+
+  int av_charHeight, av_charWidth;
+
+  boolean av_validCharWidth, av_isShowSeqFeatureHeight;
+
+  protected void updateAvConfig()
+  {
+    av_charHeight = av.getCharHeight();
+    av_charWidth = av.getCharWidth();
+    av_validCharWidth = av.isValidCharWidth();
+    av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
+  }
+
+  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
+          Color featureColour, int start, int end, int y1)
+  {
+    updateAvConfig();
+    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;
+      }
+      int pady = (y1 + av_charHeight) - av_charHeight / 5;
+      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 (offscreenRender || !av_validCharWidth)
+        {
+          continue;
+        }
+
+        g.setColor(Color.white);
+        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
+        g.drawString(String.valueOf(s), charOffset
+                + (av_charWidth * (i - start)), pady);
+
+      }
+    }
+  }
+
+  void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
+          Color featureColour, int start, int end, int y1, byte[] bs)
+  {
+    updateAvConfig();
+    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;
+      }
+      int pady = (y1 + av_charHeight) - av_charHeight / 5;
+      int ystrt = 0, yend = av_charHeight;
+      if (bs[0] != 0)
+      {
+        // signed - zero is always middle of residue line.
+        if (bs[1] < 128)
+        {
+          yend = av_charHeight * (128 - bs[1]) / 512;
+          ystrt = av_charHeight - yend / 2;
+        }
+        else
+        {
+          ystrt = av_charHeight / 2;
+          yend = av_charHeight * (bs[1] - 128) / 512;
+        }
+      }
+      else
+      {
+        yend = av_charHeight * bs[1] / 255;
+        ystrt = av_charHeight - yend;
+
+      }
+      for (i = fstart; i <= fend; i++)
+      {
+        s = seq.getCharAt(i);
+
+        if (jalview.util.Comparison.isGap(s))
+        {
+          continue;
+        }
+
+        g.setColor(featureColour);
+        int x = (i - start) * av_charWidth;
+        g.drawRect(x, y1, av_charWidth, av_charHeight);
+        g.fillRect(x, y1 + ystrt, av_charWidth, yend);
+
+        if (offscreenRender || !av_validCharWidth)
+        {
+          continue;
+        }
+
+        g.setColor(Color.black);
+        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
+        g.drawString(String.valueOf(s), charOffset
+                + (av_charWidth * (i - start)), pady);
+
+      }
+    }
+  }
+
+  BufferedImage offscreenImage;
+
+  public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
+  {
+    return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
+  }
+
+  /**
+   * This is used by the Molecule Viewer and Overview to get the accurate
+   * colourof the rendered sequence
+   */
+  public synchronized int findFeatureColour(int initialCol, final SequenceI seq,
+          int column)
+  {
+    if (!av.isShowSequenceFeatures())
+    {
+      return initialCol;
+    }
+
+    final SequenceI aseq = (seq.getDatasetSequence() != null) ? seq
+            .getDatasetSequence() : seq;
+    if (seq != lastSeq)
+    {
+      lastSeq = seq;
+      sequenceFeatures = aseq.getSequenceFeatures();
+      if (sequenceFeatures != null)
+      {
+        sfSize = sequenceFeatures.length;
+      }
+    }
+    else
+    {
+      if (sequenceFeatures != lastSeq.getSequenceFeatures())
+      {
+        sequenceFeatures = lastSeq.getSequenceFeatures();
+        if (sequenceFeatures != null)
+        {
+          sfSize = sequenceFeatures.length;
+        }
+      }
+    }
+
+    if (sequenceFeatures == null || sfSize == 0)
+    {
+      return initialCol;
+    }
+
+    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
+    {
+      return Color.white.getRGB();
+    }
+
+    // Only bother making an offscreen image if transparency is applied
+    if (transparency != 1.0f && offscreenImage == null)
+    {
+      offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+    }
+
+    currentColour = null;
+    // TODO: non-threadsafe - each rendering thread needs its own instance of
+    // the feature renderer - or this should be synchronized.
+    offscreenRender = true;
+
+    if (offscreenImage != null)
+    {
+      offscreenImage.setRGB(0, 0, initialCol);
+      drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
+
+      return offscreenImage.getRGB(0, 0);
+    }
+    else
+    {
+      drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
+
+      if (currentColour == null)
+      {
+        return initialCol;
+      }
+      else
+      {
+        return ((Integer) currentColour).intValue();
+      }
+    }
+
+  }
+
+  private volatile SequenceFeature[] sequenceFeatures;
+
+  int sfSize;
+
+  int sfindex;
+
+  int spos;
+
+  int epos;
+
+  public synchronized void drawSequence(Graphics g, final SequenceI seq,
+          int start, int end, int y1)
+  {
+    final SequenceI aseq = (seq.getDatasetSequence() != null) ? seq
+            .getDatasetSequence() : seq;
+    if (aseq.getSequenceFeatures() == null
+            || aseq.getSequenceFeatures().length == 0)
+    {
+      return;
+    }
+
+    if (g != null)
+    {
+      fm = g.getFontMetrics();
+    }
+
+    updateFeatures();
+
+    if (lastSeq == null || seq != lastSeq
+            || aseq.getSequenceFeatures() != sequenceFeatures)
+    {
+      lastSeq = seq;
+      sequenceFeatures = aseq.getSequenceFeatures();
+    }
+
+    if (transparency != 1 && g != null)
+    {
+      Graphics2D g2 = (Graphics2D) g;
+      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+              transparency));
+    }
+
+    if (!offscreenRender)
+    {
+      spos = lastSeq.findPosition(start);
+      epos = lastSeq.findPosition(end);
+    }
+
+    sfSize = sequenceFeatures.length;
+    String type;
+    for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
+    {
+      type = renderOrder[renderIndex];
+
+      if (type == null || !showFeatureOfType(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
+                && sequenceFeatures[sfindex].featureGroup.length() != 0
+                && featureGroups
+                        .containsKey(sequenceFeatures[sfindex].featureGroup)
+                && !((Boolean) featureGroups
+                        .get(sequenceFeatures[sfindex].featureGroup))
+                        .booleanValue())
+        {
+          continue;
+        }
+
+        if (!offscreenRender
+                && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
+                        .getEnd() < spos))
+        {
+          continue;
+        }
+
+        if (offscreenRender && offscreenImage == null)
+        {
+          if (sequenceFeatures[sfindex].begin <= start
+                  && sequenceFeatures[sfindex].end >= start)
+          {
+            // this is passed out to the overview and other sequence renderers
+            // (e.g. molecule viewer) to get displayed colour for rendered
+            // sequence
+            currentColour = new Integer(
+                    getColour(sequenceFeatures[sfindex]).getRGB());
+            // used to be retreived from av.featuresDisplayed
+            // currentColour = 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]))
+        {
+          if (av_isShowSeqFeatureHeight
+                  && sequenceFeatures[sfindex].score != Float.NaN)
+          {
+            renderScoreFeature(g, seq,
+                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                    getColour(sequenceFeatures[sfindex]), start, end, y1,
+                    normaliseScore(sequenceFeatures[sfindex]));
+          }
+          else
+          {
+            renderFeature(g, seq,
+                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                    getColour(sequenceFeatures[sfindex]), start, end, y1);
+          }
+        }
+
+      }
+
+    }
+
+    if (transparency != 1.0f && g != null && transparencyAvailable)
+    {
+      Graphics2D g2 = (Graphics2D) g;
+      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+              1.0f));
+    }
+  }
+
+  boolean transparencyAvailable = true;
+
+  protected void setTransparencyAvailable(boolean isTransparencyAvailable)
+  {
+    transparencyAvailable = isTransparencyAvailable;
+  }
+
+  @Override
+  public boolean isTransparencyAvailable()
+  {
+    return transparencyAvailable;
+  }
+
+  /**
+   * Called when alignment in associated view has new/modified features to
+   * discover and display.
+   * 
+   */
+  public void featuresAdded()
+  {
+    lastSeq = null;
+    findAllFeatures();
+  }
+}