JAL-2349 debugged click/drag and added yellow rubber banding (like Alphafold)
[jalview.git] / src / jalview / gui / AnnotationPanel.java
index f8172dc..ccc086c 100755 (executable)
@@ -50,15 +50,19 @@ import javax.swing.JPopupMenu;
 import javax.swing.Scrollable;
 import javax.swing.ToolTipManager;
 
+import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.ContactListI;
+import jalview.datamodel.ContactRange;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.gui.JalviewColourChooser.ColourChooserListener;
 import jalview.renderer.AnnotationRenderer;
 import jalview.renderer.AwtRenderPanelI;
+import jalview.renderer.ContactGeometry;
 import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
@@ -79,7 +83,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
 {
   enum DragMode
   {
-    Select, Resize, Undefined
+    Select, Resize, Undefined, MatrixSelect
   };
 
   String HELIX = MessageManager.getString("label.helix");
@@ -129,6 +133,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
 
   int mouseDragLastY = -1;
 
+  int firstDragX = -1;
+
+  int firstDragY = -1;
+
   DragMode dragMode = DragMode.Undefined;
 
   boolean mouseDragging = false;
@@ -355,7 +363,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         @Override
         public void colourSelected(Color c)
         {
-          HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
+          HiddenColumns hiddenColumns = av.getAlignment()
+                  .getHiddenColumns();
           for (int index : av.getColumnSelection().getSelected())
           {
             if (hiddenColumns.isVisible(index))
@@ -366,10 +375,11 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
               }
               fAnot[index].colour = c;
             }
-        }};
+          }
+        };
       };
-      JalviewColourChooser.showColourChooser(this,
-              title, Color.black, listener);
+      JalviewColourChooser.showColourChooser(this, title, Color.black,
+              listener);
     }
     else
     // HELIX, SHEET or STEM
@@ -534,8 +544,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
      */
     int height = 0;
     activeRow = -1;
-
+    int yOffset = 0;
+    // todo could reuse getRowIndexAndOffset ?
     final int y = evt.getY();
+
     for (int i = 0; i < aa.length; i++)
     {
       if (aa[i].visible)
@@ -549,12 +561,13 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         {
           activeRow = i;
         }
-        else if (aa[i].graph > 0)
+        else if (aa[i].graph != 0)
         {
           /*
            * we have clicked on a resizable graph annotation
            */
           graphStretch = i;
+          yOffset = height - y;
         }
         break;
       }
@@ -570,7 +583,44 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       return;
     }
 
-    ap.getScalePanel().mousePressed(evt);
+    if (graphStretch != -1)
+    {
+
+      if (aa[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER)
+      {
+        if (evt.isAltDown() || evt.isAltGraphDown())
+        {
+          dragMode = DragMode.MatrixSelect;
+          firstDragX = mouseDragLastX;
+          firstDragY = mouseDragLastY;
+        }
+        else
+        {
+          int currentX = getColumnForXPos(evt.getX());
+          ContactListI forCurrentX = av.getContactList(aa[graphStretch],
+                  currentX);
+          if (forCurrentX != null)
+          {
+            ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
+                    aa[graphStretch].graphHeight);
+            ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset,
+                    yOffset);
+            int fr, to;
+            fr = Math.min(cXci.cStart, cXci.cEnd);
+            to = Math.max(cXci.cStart, cXci.cEnd);
+            for (int c = fr; c <= to; c++)
+            {
+              av.getColumnSelection().addElement(c);
+            }
+            av.getColumnSelection().addElement(currentX);
+          }
+        }
+      }
+    }
+    else
+    {
+      ap.getScalePanel().mousePressed(evt);
+    }
   }
 
   /**
@@ -630,9 +680,15 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   @Override
   public void mouseReleased(MouseEvent evt)
   {
+    if (dragMode == DragMode.MatrixSelect)
+    {
+      matrixSelectRange(evt);
+    }
     graphStretch = -1;
     mouseDragLastX = -1;
     mouseDragLastY = -1;
+    firstDragX = -1;
+    firstDragY = -1;
     mouseDragging = false;
     if (dragMode == DragMode.Resize)
     {
@@ -720,10 +776,25 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
          * mostly vertical drag
          */
         dragMode = DragMode.Resize;
+
+        /*
+         * but could also be a matrix drag
+         */
+        if ((evt.isAltDown() || evt.isAltGraphDown()) && (av.getAlignment()
+                .getAlignmentAnnotation()[graphStretch].graph == AlignmentAnnotation.CUSTOMRENDERER))
+        {
+          /*
+           * dragging in a matrix
+           */
+          dragMode = DragMode.MatrixSelect;
+          firstDragX = mouseDragLastX;
+          firstDragY = mouseDragLastY;
+        }
       }
     }
 
     if (dragMode == DragMode.Undefined)
+
     {
       /*
        * drag is diagonal - defer deciding whether to
@@ -750,6 +821,15 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
           ap.paintAlignment(false, false);
         }
       }
+      else if (dragMode == DragMode.MatrixSelect)
+      {
+        /*
+         * TODO draw a rubber band for range
+         */
+        mouseDragLastX = x;
+        mouseDragLastY = y;
+        ap.paintAlignment(false, false);
+      }
       else
       {
         /*
@@ -765,6 +845,82 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     }
   }
 
+  public void matrixSelectRange(MouseEvent evt)
+  {
+    /*
+     * get geometry of drag
+     */
+    int fromY = Math.min(firstDragY, evt.getY());
+    int toY = Math.max(firstDragY, evt.getY());
+    int deltaY = toY - fromY;
+
+    int[] rowIndex = getRowIndexAndOffset(fromY,
+            av.getAlignment().getAlignmentAnnotation());
+    int[] toRowIndex = getRowIndexAndOffset(toY,
+            av.getAlignment().getAlignmentAnnotation());
+
+    if (rowIndex == null || toRowIndex == null)
+    {
+      System.out.println("Drag out of range. needs to be clipped");
+
+    }
+    if (rowIndex[0] != toRowIndex[0])
+    {
+      System.out.println("Drag went to another row. needs to be clipped");
+    }
+
+    // rectangular selection on matrix style annotation
+    AlignmentAnnotation cma = av.getAlignment()
+            .getAlignmentAnnotation()[rowIndex[0]];
+
+    int fromXp = Math.min(firstDragX, evt.getX());
+    int toXp = Math.max(firstDragX, evt.getX());
+    int lastX = getColumnForXPos(fromXp);
+    int currentX = getColumnForXPos(toXp);
+    ContactListI forLastX = av.getContactList(cma, lastX);
+    ContactListI forCurrentX = av.getContactList(cma, currentX);
+    if (forLastX != null && forCurrentX != null)
+    {
+      ContactGeometry lastXcgeom = new ContactGeometry(forLastX,
+              cma.graphHeight);
+      ContactGeometry.contactInterval lastXci = lastXcgeom.mapFor(
+              rowIndex[1],
+              rowIndex[1] + ((fromY == firstDragY) ? -deltaY : deltaY));
+      ContactGeometry cXcgeom = new ContactGeometry(forCurrentX,
+              cma.graphHeight);
+      ContactGeometry.contactInterval cXci = cXcgeom.mapFor(rowIndex[1],
+              rowIndex[1] + deltaY);
+      // mark rectangular region formed by drag
+      System.err.println("Matrix Selection from last(" + lastXci.cStart
+              + "," + lastXci.cEnd + ") to cur(" + cXci.cStart + ","
+              + cXci.cEnd + ")");
+      int fr, to;
+      fr = Math.min(lastXci.cStart, lastXci.cEnd);
+      to = Math.max(lastXci.cStart, lastXci.cEnd);
+      System.err.println("Marking " + fr + " to " + to);
+      for (int c = fr; c <= to; c++)
+      {
+        av.getColumnSelection().addElement(c);
+      }
+      fr = Math.min(cXci.cStart, cXci.cEnd);
+      to = Math.max(cXci.cStart, cXci.cEnd);
+      System.err.println("Marking " + fr + " to " + to);
+      for (int c = fr; c <= to; c++)
+      {
+        av.getColumnSelection().addElement(c);
+      }
+      fr = Math.min(lastX, currentX);
+      to = Math.max(lastX, currentX);
+
+      System.err.println("Marking " + fr + " to " + to);
+      for (int c = fr; c <= to; c++)
+      {
+        av.getColumnSelection().addElement(c);
+      }
+    }
+
+  }
+
   /**
    * Constructs the tooltip, and constructs and displays a status message, for
    * the current mouse position
@@ -776,8 +932,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   {
     int yPos = evt.getY();
     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
-
-    int row = getRowIndex(yPos, aa);
+    int rowAndOffset[] = getRowIndexAndOffset(yPos, aa);
+    int row = rowAndOffset[0];
 
     if (row == -1)
     {
@@ -785,24 +941,18 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       return;
     }
 
-    int column = (evt.getX() / av.getCharWidth())
-            + av.getRanges().getStartRes();
-    column = Math.min(column, av.getRanges().getEndRes());
-
-    if (av.hasHiddenColumns())
-    {
-      column = av.getAlignment().getHiddenColumns()
-              .visibleToAbsoluteColumn(column);
-    }
+    int column = getColumnForXPos(evt.getX());
 
     AlignmentAnnotation ann = aa[row];
     if (row > -1 && ann.annotations != null
             && column < ann.annotations.length)
     {
-      String toolTip = buildToolTip(ann, column, aa);
+      String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av,
+              ap);
       setToolTipText(toolTip == null ? null
               : JvSwingUtils.wrapTooltip(true, toolTip));
-      String msg = getStatusMessage(av.getAlignment(), column, ann);
+      String msg = getStatusMessage(av.getAlignment(), column, ann,
+              rowAndOffset[1], av);
       ap.alignFrame.setStatus(msg);
     }
     else
@@ -812,6 +962,19 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     }
   }
 
+  private int getColumnForXPos(int x)
+  {
+    int column = (x / av.getCharWidth()) + av.getRanges().getStartRes();
+    column = Math.min(column, av.getRanges().getEndRes());
+
+    if (av.hasHiddenColumns())
+    {
+      column = av.getAlignment().getHiddenColumns()
+              .visibleToAbsoluteColumn(column);
+    }
+    return column;
+  }
+
   /**
    * Answers the index in the annotations array of the visible annotation at the
    * given y position. This is done by adding the heights of visible annotations
@@ -828,23 +991,38 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     {
       return -1;
     }
+    return getRowIndexAndOffset(yPos, aa)[0];
+  }
+
+  static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa)
+  {
+    int[] res = new int[2];
+    if (aa == null)
+    {
+      res[0] = -1;
+      res[1] = 0;
+      return res;
+    }
     int row = -1;
-    int height = 0;
+    int height = 0, lheight = 0;
 
     for (int i = 0; i < aa.length; i++)
     {
       if (aa[i].visible)
       {
+        lheight = height;
         height += aa[i].height;
       }
 
       if (height > yPos)
       {
         row = i;
+        res[0] = row;
+        res[1] = height - yPos;
         break;
       }
     }
-    return row;
+    return res;
   }
 
   /**
@@ -855,9 +1033,11 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
    * @param ann
    * @param column
    * @param anns
+   * @param rowAndOffset
    */
   static String buildToolTip(AlignmentAnnotation ann, int column,
-          AlignmentAnnotation[] anns)
+          AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av,
+          AlignmentPanel ap)
   {
     String tooltip = null;
     if (ann.graphGroup > -1)
@@ -889,7 +1069,21 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     {
       tooltip = ann.annotations[column].description;
     }
+    // TODO abstract tooltip generator so different implementations can be built
+    if (ann.graph == AlignmentAnnotation.CUSTOMRENDERER)
+    {
+      ContactListI clist = av.getContactList(ann, column);
+      if (clist != null)
+      {
+        ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight);
+        ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset,
+                rowAndOffset);
+        tooltip += "Contact from " + ci.cStart + " to " + ci.cEnd;
 
+        // ap.getStructureSelectionManager().mouseOverSequence(ann.sequenceRef,
+        // new int[] {column, ci.cStart,ci.cEnd}, -1, null)
+      }
+    }
     return tooltip;
   }
 
@@ -899,9 +1093,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
    * @param al
    * @param column
    * @param ann
+   * @param rowAndOffset
    */
   static String getStatusMessage(AlignmentI al, int column,
-          AlignmentAnnotation ann)
+          AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av)
   {
     /*
      * show alignment column and annotation description if any
@@ -999,7 +1194,9 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   }
 
   private volatile boolean imageFresh = false;
-  private Rectangle visibleRect = new Rectangle(), clipBounds = new Rectangle();
+
+  private Rectangle visibleRect = new Rectangle(),
+          clipBounds = new Rectangle();
 
   /**
    * DOCUMENT ME!
@@ -1010,27 +1207,28 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   @Override
   public void paintComponent(Graphics g)
   {
-         
-         // BH: note that this method is generally recommended to 
-         // call super.paintComponent(g). Otherwise, the children of this
-         // component will not be rendered. That is not needed here 
-         // because AnnotationPanel does not have any children. It is
-         // just a JPanel contained in a JViewPort. 
+
+    // BH: note that this method is generally recommended to
+    // call super.paintComponent(g). Otherwise, the children of this
+    // component will not be rendered. That is not needed here
+    // because AnnotationPanel does not have any children. It is
+    // just a JPanel contained in a JViewPort.
 
     computeVisibleRect(visibleRect);
-    
+
     g.setColor(Color.white);
     g.fillRect(0, 0, visibleRect.width, visibleRect.height);
 
     if (image != null)
     {
-       // BH 2018 optimizing generation of new Rectangle().
-      if (fastPaint || (visibleRect.width != (clipBounds = g.getClipBounds(clipBounds)).width)
-            || (visibleRect.height != clipBounds.height))
+      // BH 2018 optimizing generation of new Rectangle().
+      if (fastPaint
+              || (visibleRect.width != (clipBounds = g
+                      .getClipBounds(clipBounds)).width)
+              || (visibleRect.height != clipBounds.height))
       {
 
-         
-         g.drawImage(image, 0, 0, this);
+        g.drawImage(image, 0, 0, this);
         fastPaint = false;
         return;
       }
@@ -1077,11 +1275,13 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       gg.setColor(Color.white);
       gg.fillRect(0, 0, imgWidth, image.getHeight());
       imageFresh = true;
-    } else {
-        gg = (Graphics2D) image.getGraphics();
+    }
+    else
+    {
+      gg = (Graphics2D) image.getGraphics();
 
     }
-    
+
     drawComponent(gg, av.getRanges().getStartRes(),
             av.getRanges().getEndRes() + 1);
     gg.dispose();
@@ -1117,19 +1317,22 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
 
     Graphics2D gg = (Graphics2D) image.getGraphics();
 
-    gg.copyArea(0, 0, imgWidth, getHeight(),
-            -horizontal * av.getCharWidth(), 0);
-
-    if (horizontal > 0) // scrollbar pulled right, image to the left
-    {
-      transX = (er - sr - horizontal) * av.getCharWidth();
-      sr = er - horizontal;
-    }
-    else if (horizontal < 0)
+    if (imgWidth > Math.abs(horizontal * av.getCharWidth()))
     {
-      er = sr - horizontal;
-    }
+      // scroll is less than imgWidth away so can re-use buffered graphics
+      gg.copyArea(0, 0, imgWidth, getHeight(),
+              -horizontal * av.getCharWidth(), 0);
 
+      if (horizontal > 0) // scrollbar pulled right, image to the left
+      {
+        transX = (er - sr - horizontal) * av.getCharWidth();
+        sr = er - horizontal;
+      }
+      else if (horizontal < 0)
+      {
+        er = sr - horizontal;
+      }
+    }
     gg.translate(transX, 0);
 
     drawComponent(gg, sr, er);
@@ -1137,7 +1340,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     gg.translate(-transX, 0);
 
     gg.dispose();
-    
+
     fastPaint = true;
 
     // Call repaint on alignment panel so that repaints from other alignment
@@ -1233,6 +1436,17 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     {
       fadedImage = oldFaded;
     }
+    if (dragMode == DragMode.MatrixSelect)
+    {
+      g.setColor(Color.yellow);
+      g.drawRect(Math.min(firstDragX, mouseDragLastX),
+              Math.min(firstDragY, mouseDragLastY),
+              Math.max(firstDragX, mouseDragLastX)
+                      - Math.min(firstDragX, mouseDragLastX),
+              Math.max(firstDragY, mouseDragLastY)
+                      - Math.min(firstDragY, mouseDragLastY));
+
+    }
   }
 
   @Override
@@ -1281,7 +1495,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     ap = null;
     image = null;
     fadedImage = null;
-//    gg = null;
+    // gg = null;
     _mwl = null;
 
     /*