JAL-3093 refactor (+test) Annotation Labels tooltip ready for reuse
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 1 Oct 2018 14:06:00 +0000 (15:06 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 1 Oct 2018 14:06:00 +0000 (15:06 +0100)
src/jalview/gui/AnnotationLabels.java
test/jalview/gui/AnnotationLabelsTest.java [new file with mode: 0644]

index 6f8b225..2517a9f 100755 (executable)
@@ -51,12 +51,9 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.geom.AffineTransform;
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.regex.Pattern;
 
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JMenuItem;
@@ -73,6 +70,10 @@ import javax.swing.ToolTipManager;
 public class AnnotationLabels extends JPanel
         implements MouseListener, MouseMotionListener, ActionListener
 {
+  private static final String HTML_END_TAG = "</html>";
+
+  private static final String HTML_START_TAG = "<html>";
+
   /**
    * width in pixels within which height adjuster arrows are shown and active
    */
@@ -83,9 +84,6 @@ public class AnnotationLabels extends JPanel
    */
   private static int HEIGHT_ADJUSTER_HEIGHT = 10;
 
-  private static final Pattern LEFT_ANGLE_BRACKET_PATTERN = Pattern
-          .compile("<");
-
   private static final Font font = new Font("Arial", Font.PLAIN, 11);
 
   private static final String TOGGLE_LABELSCALE = MessageManager
@@ -708,70 +706,70 @@ public class AnnotationLabels extends JPanel
       AlignmentAnnotation aa = ap.av.getAlignment()
               .getAlignmentAnnotation()[selectedRow];
 
-      StringBuffer desc = new StringBuffer();
-      if (aa.description != null
-              && !aa.description.equals("New description"))
-      {
-        // TODO: we could refactor and merge this code with the code in
-        // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
-        // tooltips
-        desc.append(aa.getDescription(true).trim());
-        // check to see if the description is an html fragment.
-        if (desc.length() < 6 || (desc.substring(0, 6).toLowerCase()
-                .indexOf("<html>") < 0))
-        {
-          // clean the description ready for embedding in html
-          desc = new StringBuffer(LEFT_ANGLE_BRACKET_PATTERN.matcher(desc)
-                  .replaceAll("&lt;"));
-          desc.insert(0, "<html>");
-        }
-        else
-        {
-          // remove terminating html if any
-          int i = desc.substring(desc.length() - 7).toLowerCase()
-                  .lastIndexOf("</html>");
-          if (i > -1)
-          {
-            desc.setLength(desc.length() - 7 + i);
-          }
-        }
-        if (aa.hasScore())
-        {
-          desc.append("<br/>");
-        }
-        // if (aa.hasProperties())
-        // {
-        // desc.append("<table>");
-        // for (String prop : aa.getProperties())
-        // {
-        // desc.append("<tr><td>" + prop + "</td><td>"
-        // + aa.getProperty(prop) + "</td><tr>");
-        // }
-        // desc.append("</table>");
-        // }
-      }
-      else
+      String desc = getTooltip(aa);
+      this.setToolTipText(desc);
+    }
+  }
+
+  /**
+   * Answers a tooltip, formatted as html, containing the annotation description
+   * (prefixed by associated sequence id if applicable), and the annotation
+   * (non-positional) score if it has one. Answers null if neither description
+   * nor score is found.
+   * 
+   * @param aa
+   * @return
+   */
+  static String getTooltip(AlignmentAnnotation aa)
+  {
+    if (aa == null)
+    {
+      return null;
+    }
+    StringBuilder tooltip = new StringBuilder();
+    if (aa.description != null && !aa.description.equals("New description"))
+    {
+      // TODO: we could refactor and merge this code with the code in
+      // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
+      // tooltips
+      String desc = aa.getDescription(true).trim();
+      if (!desc.toLowerCase().startsWith(HTML_START_TAG))
       {
-        // begin the tooltip's html fragment
-        desc.append("<html>");
-        if (aa.hasScore())
-        {
-          // TODO: limit precision of score to avoid noise from imprecise
-          // doubles
-          // (64.7 becomes 64.7+/some tiny value).
-          desc.append(" Score: " + aa.score);
-        }
+        tooltip.append(HTML_START_TAG);
+        desc = desc.replace("<", "&lt;");
       }
-      if (desc.length() > 6)
+      else if (desc.toLowerCase().endsWith(HTML_END_TAG))
       {
-        desc.append("</html>");
-        this.setToolTipText(desc.toString());
+        desc = desc.substring(0, desc.length() - HTML_END_TAG.length());
       }
-      else
+      tooltip.append(desc);
+    }
+    else
+    {
+      // begin the tooltip's html fragment
+      tooltip.append(HTML_START_TAG);
+    }
+    if (aa.hasScore())
+    {
+      if (tooltip.length() > HTML_START_TAG.length())
       {
-        this.setToolTipText(null);
+        tooltip.append("<br/>");
       }
+      // TODO: limit precision of score to avoid noise from imprecise
+      // doubles
+      // (64.7 becomes 64.7+/some tiny value).
+      tooltip.append(" Score: ").append(String.valueOf(aa.score));
     }
+
+    if (tooltip.length() > HTML_START_TAG.length())
+    {
+      return tooltip.append(HTML_END_TAG).toString();
+    }
+
+    /*
+     * nothing in the tooltip (except "<html>")
+     */
+    return null;
   }
 
   /**
diff --git a/test/jalview/gui/AnnotationLabelsTest.java b/test/jalview/gui/AnnotationLabelsTest.java
new file mode 100644 (file)
index 0000000..31839a9
--- /dev/null
@@ -0,0 +1,105 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Sequence;
+
+import org.testng.annotations.Test;
+
+public class AnnotationLabelsTest
+{
+  @Test(groups = "Functional")
+  public void testGetTooltip()
+  {
+    assertNull(AnnotationLabels.getTooltip(null));
+
+    /*
+     * simple description only
+     */
+    AlignmentAnnotation ann = new AlignmentAnnotation("thelabel", "thedesc",
+            null);
+    String expected = "<html>thedesc</html>";
+    assertEquals(AnnotationLabels.getTooltip(ann), expected);
+
+    /*
+     * description needing html encoding
+     * (no idea why '<' is encoded but '>' is not)
+     */
+    ann.description = "TCoffee scores < 56 and > 28";
+    expected = "<html>TCoffee scores &lt; 56 and > 28</html>";
+    assertEquals(AnnotationLabels.getTooltip(ann), expected);
+
+    /*
+     * description already html formatted
+     */
+    ann.description = "<html>hello world</html>";
+    assertEquals(AnnotationLabels.getTooltip(ann), ann.description);
+
+    /*
+     * simple description and score
+     */
+    ann.description = "hello world";
+    ann.setScore(2.34d);
+    expected = "<html>hello world<br/> Score: 2.34</html>";
+    assertEquals(AnnotationLabels.getTooltip(ann), expected);
+
+    /*
+     * html description and score
+     */
+    ann.description = "<html>hello world</html>";
+    ann.setScore(2.34d);
+    expected = "<html>hello world<br/> Score: 2.34</html>";
+    assertEquals(AnnotationLabels.getTooltip(ann), expected);
+
+    /*
+     * score, no description
+     */
+    ann.description = " ";
+    assertEquals(AnnotationLabels.getTooltip(ann),
+            "<html> Score: 2.34</html>");
+    ann.description = null;
+    assertEquals(AnnotationLabels.getTooltip(ann),
+            "<html> Score: 2.34</html>");
+    
+    /*
+     * sequenceref, simple description
+     */
+    ann.description = "Count < 12";
+    ann.sequenceRef = new Sequence("Seq1", "MLRIQST");
+    ann.hasScore = false;
+    ann.score = Double.NaN;
+    expected = "<html>Seq1 : Count &lt; 12</html>";
+    assertEquals(AnnotationLabels.getTooltip(ann), expected);
+
+    /*
+     * sequenceref, html description, score
+     */
+    ann.description = "<html>Score < 4.8</html>";
+    ann.sequenceRef = new Sequence("Seq1", "MLRIQST");
+    ann.setScore(-2.1D);
+    expected = "<html>Seq1 : Score < 4.8<br/> Score: -2.1</html>";
+    assertEquals(AnnotationLabels.getTooltip(ann), expected);
+
+    /*
+     * no score, null description
+     */
+    ann.description = null;
+    ann.hasScore = false;
+    ann.score = Double.NaN;
+    assertNull(AnnotationLabels.getTooltip(ann));
+
+    /*
+     * no score, empty description, sequenceRef
+     */
+    ann.description = "";
+    assertEquals(AnnotationLabels.getTooltip(ann), "<html>Seq1 :</html>");
+
+    /*
+     * no score, empty description, no sequenceRef
+     */
+    ann.sequenceRef = null;
+    assertNull(AnnotationLabels.getTooltip(ann));
+  }
+}