JAL-1767 refactoring and tidying of RotatableCanvas and related
[jalview.git] / src / jalview / gui / RotatableCanvas.java
index dc33b36..617180f 100755 (executable)
 package jalview.gui;
 
 import jalview.api.RotatableCanvasI;
-import jalview.bin.Cache;
+import jalview.datamodel.Point;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequencePoint;
 import jalview.math.RotatableMatrix;
+import jalview.math.RotatableMatrix.Axis;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 
@@ -50,30 +51,20 @@ import javax.swing.JPanel;
 import javax.swing.ToolTipManager;
 
 /**
- * DOCUMENT ME!
- * 
- * @author $author$
- * @version $Revision$
+ * Models a Panel on which a set of points, and optionally x/y/z axes, can be
+ * drawn, and rotated or zoomed with the mouse
  */
 public class RotatableCanvas extends JPanel implements MouseListener,
         MouseMotionListener, KeyListener, RotatableCanvasI
 {
-  RotatableMatrix idmat = new RotatableMatrix(3, 3);
-
-  RotatableMatrix objmat = new RotatableMatrix(3, 3);
-
-  RotatableMatrix rotmat = new RotatableMatrix(3, 3);
+  private static final int DIMS = 3;
 
   // RubberbandRectangle rubberband;
   boolean drawAxes = true;
 
-  int omx = 0;
-
-  int mx = 0;
+  int mouseX = 0;
 
-  int omy = 0;
-
-  int my = 0;
+  int mouseY = 0;
 
   Image img;
 
@@ -81,13 +72,13 @@ public class RotatableCanvas extends JPanel implements MouseListener,
 
   Dimension prefsize;
 
-  float[] centre = new float[3];
+  Point centre;
 
-  float[] width = new float[3];
+  float[] width = new float[DIMS];
 
-  float[] max = new float[3];
+  float[] max = new float[DIMS];
 
-  float[] min = new float[3];
+  float[] min = new float[DIMS];
 
   float maxwidth;
 
@@ -97,9 +88,9 @@ public class RotatableCanvas extends JPanel implements MouseListener,
 
   Vector<SequencePoint> points;
 
-  float[][] orig;
+  Point[] orig;
 
-  float[][] axes;
+  Point[] axisEndPoints;
 
   int startx;
 
@@ -131,10 +122,16 @@ public class RotatableCanvas extends JPanel implements MouseListener,
 
   boolean first = true;
 
-  public RotatableCanvas(AlignmentPanel ap)
+  /**
+   * Constructor
+   * 
+   * @param panel
+   */
+  public RotatableCanvas(AlignmentPanel panel)
   {
-    this.av = ap.av;
-    this.ap = ap;
+    this.av = panel.av;
+    this.ap = panel;
+    axisEndPoints = new Point[DIMS];
 
     addMouseWheelListener(new MouseWheelListener()
     {
@@ -181,40 +178,15 @@ public class RotatableCanvas extends JPanel implements MouseListener,
       ToolTipManager.sharedInstance().setDismissDelay(10000);
     }
     prefsize = getPreferredSize();
-    orig = new float[npoint][3];
+    orig = new Point[npoint];
 
     for (int i = 0; i < npoint; i++)
     {
       SequencePoint sp = points.elementAt(i);
-
-      for (int j = 0; j < 3; j++)
-      {
-        orig[i][j] = sp.coord[j];
-      }
+      orig[i] = sp.coord;
     }
 
-    // Initialize the matrices to identity
-    for (int i = 0; i < 3; i++)
-    {
-      for (int j = 0; j < 3; j++)
-      {
-        if (i != j)
-        {
-          idmat.addElement(i, j, 0);
-          objmat.addElement(i, j, 0);
-          rotmat.addElement(i, j, 0);
-        }
-        else
-        {
-          idmat.addElement(i, j, 0);
-          objmat.addElement(i, j, 0);
-          rotmat.addElement(i, j, 0);
-        }
-      }
-    }
-
-    axes = new float[3][3];
-    initAxes();
+    resetAxes();
 
     findCentre();
     findWidth();
@@ -222,86 +194,56 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     scale = findScale();
     if (first)
     {
-
       addMouseListener(this);
-
       addMouseMotionListener(this);
     }
     first = false;
   }
 
-  public void initAxes()
+  /**
+   * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to
+   * back (so obscured in a 2-D display)
+   */
+  public void resetAxes()
   {
-    for (int i = 0; i < 3; i++)
-    {
-      for (int j = 0; j < 3; j++)
-      {
-        if (i != j)
-        {
-          axes[i][j] = 0;
-        }
-        else
-        {
-          axes[i][j] = 1;
-        }
-      }
-    }
+    axisEndPoints[0] = new Point(1f, 0f, 0f);
+    axisEndPoints[1] = new Point(0f, 1f, 0f);
+    axisEndPoints[2] = new Point(0f, 0f, 1f);
   }
 
   /**
-   * DOCUMENT ME!
+   * Computes and saves the maximum and minimum (x, y, z) positions of any
+   * sequence point, and also the min-max range (width) for each dimension, and
+   * the maximum width for all dimensions
    */
   public void findWidth()
   {
-    max = new float[3];
-    min = new float[3];
+    max = new float[DIMS];
+    min = new float[DIMS];
 
-    max[0] = (float) -1e30;
-    max[1] = (float) -1e30;
-    max[2] = (float) -1e30;
+    max[0] = Float.MIN_VALUE;
+    max[1] = Float.MIN_VALUE;
+    max[2] = Float.MIN_VALUE;
 
-    min[0] = (float) 1e30;
-    min[1] = (float) 1e30;
-    min[2] = (float) 1e30;
+    min[0] = Float.MAX_VALUE;
+    min[1] = Float.MAX_VALUE;
+    min[2] = Float.MAX_VALUE;
 
-    for (int i = 0; i < 3; i++)
+    for (SequencePoint sp : points)
     {
-      for (int j = 0; j < npoint; j++)
-      {
-        SequencePoint sp = points.elementAt(j);
-
-        if (sp.coord[i] >= max[i])
-        {
-          max[i] = sp.coord[i];
-        }
-
-        if (sp.coord[i] <= min[i])
-        {
-          min[i] = sp.coord[i];
-        }
-      }
+      max[0] = Math.max(max[0], sp.coord.x);
+      max[1] = Math.max(max[1], sp.coord.y);
+      max[2] = Math.max(max[2], sp.coord.z);
+      min[0] = Math.min(min[0], sp.coord.x);
+      min[1] = Math.min(min[1], sp.coord.y);
+      min[2] = Math.min(min[2], sp.coord.z);
     }
 
-    // System.out.println("xmax " + max[0] + " min " + min[0]);
-    // System.out.println("ymax " + max[1] + " min " + min[1]);
-    // System.out.println("zmax " + max[2] + " min " + min[2]);
     width[0] = Math.abs(max[0] - min[0]);
     width[1] = Math.abs(max[1] - min[1]);
     width[2] = Math.abs(max[2] - min[2]);
 
-    maxwidth = width[0];
-
-    if (width[1] > width[0])
-    {
-      maxwidth = width[1];
-    }
-
-    if (width[2] > width[1])
-    {
-      maxwidth = width[2];
-    }
-
-    // System.out.println("Maxwidth = " + maxwidth);
+    maxwidth = Math.max(width[0], Math.max(width[1], width[2]));
   }
 
   /**
@@ -339,20 +281,17 @@ public class RotatableCanvas extends JPanel implements MouseListener,
   }
 
   /**
-   * DOCUMENT ME!
+   * Computes and saves the position of the centre of the view
    */
   public void findCentre()
   {
-    // Find centre coordinate
     findWidth();
 
-    centre[0] = (max[0] + min[0]) / 2;
-    centre[1] = (max[1] + min[1]) / 2;
-    centre[2] = (max[2] + min[2]) / 2;
+    float x = (max[0] + min[0]) / 2;
+    float y = (max[1] + min[1]) / 2;
+    float z = (max[2] + min[2]) / 2;
 
-    // System.out.println("Centre x " + centre[0]);
-    // System.out.println("Centre y " + centre[1]);
-    // System.out.println("Centre z " + centre[2]);
+    centre = new Point(x, y, z);
   }
 
   /**
@@ -434,31 +373,37 @@ public class RotatableCanvas extends JPanel implements MouseListener,
   }
 
   /**
-   * DOCUMENT ME!
+   * Resets the view to initial state (no rotation)
+   */
+  public void resetView()
+  {
+    img = null;
+    resetAxes();
+  }
+
+  /**
+   * Draws lines for the x, y, z axes
    * 
    * @param g
-   *          DOCUMENT ME!
    */
   public void drawAxes(Graphics g)
   {
 
     g.setColor(Color.yellow);
 
-    for (int i = 0; i < 3; i++)
+    for (int i = 0; i < DIMS; i++)
     {
       g.drawLine(getWidth() / 2, getHeight() / 2,
-              (int) ((axes[i][0] * scale * max[0]) + (getWidth() / 2)),
-              (int) ((axes[i][1] * scale * max[1]) + (getHeight() / 2)));
+              (int) ((axisEndPoints[i].x * scale * max[0]) + (getWidth() / 2)),
+              (int) ((axisEndPoints[i].y * scale * max[1]) + (getHeight() / 2)));
     }
   }
 
   /**
-   * DOCUMENT ME!
+   * Fills the background with the specified colour
    * 
    * @param g
-   *          DOCUMENT ME!
    * @param col
-   *          DOCUMENT ME!
    */
   public void drawBackground(Graphics g, Color col)
   {
@@ -467,14 +412,16 @@ public class RotatableCanvas extends JPanel implements MouseListener,
   }
 
   /**
-   * DOCUMENT ME!
+   * Draws points (6x6 squares) for the sequences of the PCA, and labels
+   * (sequence names) if configured to do so. The sequence points colours are
+   * taken from the sequence ids in the alignment (converting black to white).
+   * Sequences 'at the back' (z-coordinate is negative) are shaded slightly
+   * darker to help give a 3-D sensation.
    * 
    * @param g
-   *          DOCUMENT ME!
    */
   public void drawScene(Graphics g1)
   {
-
     Graphics2D g = (Graphics2D) g1;
 
     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
@@ -486,10 +433,10 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     for (int i = 0; i < npoint; i++)
     {
       SequencePoint sp = points.elementAt(i);
-      int x = (int) ((sp.coord[0] - centre[0]) * scale) + halfwidth;
-      int y = (int) ((sp.coord[1] - centre[1]) * scale)
+      int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth;
+      int y = (int) ((sp.coord.y - centre.y) * scale)
               + halfheight;
-      float z = sp.coord[1] - centre[2];
+      float z = sp.coord.y - centre.z; // todo sp.coord.z JAL-2963
 
       SequenceI sequence = sp.getSequence();
       if (av.getSequenceColour(sequence) == Color.black)
@@ -531,53 +478,20 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     // }
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public Dimension minimumsize()
-  {
-    return prefsize;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public Dimension preferredsize()
-  {
-    return prefsize;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param evt
-   *          DOCUMENT ME!
-   */
   @Override
   public void keyTyped(KeyEvent evt)
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param evt
-   *          DOCUMENT ME!
-   */
   @Override
   public void keyReleased(KeyEvent evt)
   {
   }
 
   /**
-   * DOCUMENT ME!
+   * Responds to up or down arrow key by zooming in or out, respectively
    * 
    * @param evt
-   *          DOCUMENT ME!
    */
   @Override
   public void keyPressed(KeyEvent evt)
@@ -594,7 +508,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     }
     else if (evt.getKeyChar() == 's')
     {
-      Cache.log.warn("DEBUG: Rectangle selection");
+      // Cache.log.warn("DEBUG: Rectangle selection");
       // todo not yet enabled as rectx2, recty2 are always -1
       // need to set them in mouseDragged
       if ((rectx2 != -1) && (recty2 != -1))
@@ -638,11 +552,8 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     int x = evt.getX();
     int y = evt.getY();
 
-    mx = x;
-    my = y;
-
-    omx = mx;
-    omy = my;
+    mouseX = x;
+    mouseY = y;
 
     startx = x;
     starty = y;
@@ -694,16 +605,21 @@ public class RotatableCanvas extends JPanel implements MouseListener,
   }
 
   /**
-   * DOCUMENT ME!
+   * Action handler for a mouse drag. Rotates the display around the X axis (for
+   * up/down mouse movement) and/or the Y axis (for left/right mouse movement).
    * 
    * @param evt
-   *          DOCUMENT ME!
    */
   @Override
   public void mouseDragged(MouseEvent evt)
   {
-    mx = evt.getX();
-    my = evt.getY();
+    int xPos = evt.getX();
+    int yPos = evt.getY();
+
+    if (xPos == mouseX && yPos == mouseY)
+    {
+      return;
+    }
 
     // Check if this is a rectangle drawing drag
     if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
@@ -713,34 +629,55 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     }
     else
     {
-      rotmat.setIdentity();
+      /*
+       * get the identity transformation...
+       */
+      RotatableMatrix rotmat = new RotatableMatrix();
+
+      /*
+       * rotate around the X axis for change in Y
+       * (mouse movement up/down); note we are equating a
+       * number of pixels with degrees of rotation here!
+       */
+      if (yPos != mouseY)
+      {
+        rotmat.rotate(yPos - mouseY, Axis.X);
+      }
 
-      rotmat.rotate(my - omy, 'x');
-      rotmat.rotate(mx - omx, 'y');
+      /*
+       * rotate around the Y axis for change in X
+       * (mouse movement left/right)
+       */
+      if (xPos != mouseX)
+      {
+        rotmat.rotate(xPos - mouseX, Axis.Y);
+      }
 
+      /*
+       * apply the composite transformation to sequence points
+       */
       for (int i = 0; i < npoint; i++)
       {
         SequencePoint sp = points.elementAt(i);
-        sp.coord[0] -= centre[0];
-        sp.coord[1] -= centre[1];
-        sp.coord[2] -= centre[2];
+        sp.translateBack(centre);
 
         // Now apply the rotation matrix
         sp.coord = rotmat.vectorMultiply(sp.coord);
 
         // Now translate back again
-        sp.coord[0] += centre[0];
-        sp.coord[1] += centre[1];
-        sp.coord[2] += centre[2];
+        sp.translate(centre);
       }
 
-      for (int i = 0; i < 3; i++)
+      /*
+       * rotate the x/y/z axis positions
+       */
+      for (int i = 0; i < DIMS; i++)
       {
-        axes[i] = rotmat.vectorMultiply(axes[i]);
+        axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]);
       }
 
-      omx = mx;
-      omy = my;
+      mouseX = xPos;
+      mouseY = yPos;
 
       paint(this.getGraphics());
     }
@@ -761,9 +698,9 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     for (int i = 0; i < npoint; i++)
     {
       SequencePoint sp = points.elementAt(i);
-      int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale)
+      int tmp1 = (int) (((sp.coord.x - centre.x) * scale)
               + (getWidth() / 2.0));
-      int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale)
+      int tmp2 = (int) (((sp.coord.y - centre.y) * scale)
               + (getHeight() / 2.0));
 
       if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
@@ -800,14 +737,15 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     for (int i = 0; i < npoint; i++)
     {
       SequencePoint sp = points.elementAt(i);
-      int px = (int) ((sp.coord[0] - centre[0]) * scale)
+      int px = (int) ((sp.coord.x - centre.x) * scale)
               + halfwidth;
-      int py = (int) ((sp.coord[1] - centre[1]) * scale)
+      int py = (int) ((sp.coord.y - centre.y) * scale)
               + halfheight;
 
       if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))
       {
         found = i;
+        break;
       }
     }
 
@@ -821,6 +759,12 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     }
   }
 
+  /**
+   * Answers the panel the PCA is associated with (all panels for this alignment
+   * if 'associate with all panels' is selected).
+   * 
+   * @return
+   */
   AlignmentPanel[] getAssociatedPanels()
   {
     if (applyToAllViews)
@@ -832,20 +776,4 @@ public class RotatableCanvas extends JPanel implements MouseListener,
       return new AlignmentPanel[] { ap };
     }
   }
-
-  /**
-   * 
-   * @return x,y,z positions of point s (index into points) under current
-   *         transform.
-   */
-  public double[] getPointPosition(int s)
-  {
-    double[] pts = new double[3];
-    float[] p = points.elementAt(s).coord;
-    pts[0] = p[0];
-    pts[1] = p[1];
-    pts[2] = p[2];
-    return pts;
-  }
-
 }