JAL-2272 Cmd-drag to group edit sequences on Mac (Ctrl-click for popup
[jalview.git] / src / jalview / gui / SeqPanel.java
index 41c3ce1..3266fab 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.gui;
 
 import jalview.api.AlignViewportI;
+import jalview.bin.Cache;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
@@ -42,6 +43,7 @@ import jalview.structure.VamsasSource;
 import jalview.util.Comparison;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.BorderLayout;
@@ -54,11 +56,12 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
 
 /**
@@ -192,8 +195,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
 
       int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
-              + hgap
-              + seqCanvas.getAnnotationHeight();
+              + hgap + seqCanvas.getAnnotationHeight();
 
       int y = evt.getY();
       y -= hgap;
@@ -250,8 +252,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
 
       int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
-              + hgap
-              + seqCanvas.getAnnotationHeight();
+              + hgap + seqCanvas.getAnnotationHeight();
 
       y -= hgap;
 
@@ -511,6 +512,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
   void insertNucAtCursor(boolean group, String nuc)
   {
+    // TODO not called - delete?
     groupEditing = group;
     startseq = seqCanvas.cursorY;
     lastres = seqCanvas.cursorX;
@@ -581,6 +583,13 @@ public class SeqPanel extends JPanel implements MouseListener,
     mouseDragging = false;
     mouseWheelPressed = false;
 
+    if (evt.isPopupTrigger()) // Windows: mouseReleased
+    {
+      showPopupMenu(evt);
+      evt.consume();
+      return;
+    }
+
     if (!editingSeqs)
     {
       doMouseReleasedDefineMode(evt);
@@ -601,19 +610,20 @@ public class SeqPanel extends JPanel implements MouseListener,
   {
     lastMousePress = evt.getPoint();
 
-    if (javax.swing.SwingUtilities.isMiddleMouseButton(evt))
+    if (SwingUtilities.isMiddleMouseButton(evt))
     {
       mouseWheelPressed = true;
       return;
     }
 
-    if (evt.isShiftDown() || evt.isAltDown() || evt.isControlDown())
+    boolean isControlDown = Platform.isControlDown(evt);
+    if (evt.isShiftDown() || isControlDown)
     {
-      if (evt.isAltDown() || evt.isControlDown())
+      editingSeqs = true;
+      if (isControlDown)
       {
         groupEditing = true;
       }
-      editingSeqs = true;
     }
     else
     {
@@ -668,11 +678,12 @@ public class SeqPanel extends JPanel implements MouseListener,
   @Override
   public void highlightSequence(SearchResults results)
   {
-    if (results == null || results.equals(lastSearchResults)) {
+    if (results == null || results.equals(lastSearchResults))
+    {
       return;
     }
     lastSearchResults = results;
-    
+
     if (av.isFollowHighlight())
     {
       /*
@@ -681,7 +692,7 @@ public class SeqPanel extends JPanel implements MouseListener,
        * 
        * @see AlignmentPanel#adjustmentValueChanged
        */
-      ap.setFollowingComplementScroll(true);
+      ap.setDontScrollComplement(true);
       if (ap.scrollToPosition(results, false))
       {
         seqCanvas.revalidate();
@@ -696,6 +707,7 @@ public class SeqPanel extends JPanel implements MouseListener,
   {
     return this.ap == null ? null : this.ap.av;
   }
+
   @Override
   public void updateColours(SequenceI seq, int index)
   {
@@ -767,9 +779,9 @@ public class SeqPanel extends JPanel implements MouseListener,
     if (av.isShowSequenceFeatures())
     {
       int rpos;
-      List<SequenceFeature> features = ap.getFeatureRenderer().findFeaturesAtRes(
-              sequence.getDatasetSequence(),
-              rpos = sequence.findPosition(res));
+      List<SequenceFeature> features = ap.getFeatureRenderer()
+              .findFeaturesAtRes(sequence.getDatasetSequence(),
+                      rpos = sequence.findPosition(res));
       seqARep.appendFeatures(tooltipText, rpos, features,
               this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
     }
@@ -801,6 +813,7 @@ public class SeqPanel extends JPanel implements MouseListener,
    * 
    * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
    */
+  @Override
   public Point getToolTipLocation(MouseEvent event)
   {
     int x = event.getX(), w = getWidth();
@@ -821,6 +834,21 @@ public class SeqPanel extends JPanel implements MouseListener,
   String lastTooltip;
 
   /**
+   * set when the current UI interaction has resulted in a change that requires
+   * overview shading to be recalculated. this could be changed to something
+   * more expressive that indicates what actually has changed, so selective
+   * redraws can be applied
+   */
+  private boolean needOverviewUpdate = false; // TODO: refactor to avcontroller
+
+  /**
+   * set if av.getSelectionGroup() refers to a group that is defined on the
+   * alignment view, rather than a transient selection
+   */
+  // private boolean editingDefinedGroup = false; // TODO: refactor to
+  // avcontroller or viewModel
+
+  /**
    * Set status message in alignment panel
    * 
    * @param sequence
@@ -839,7 +867,8 @@ public class SeqPanel extends JPanel implements MouseListener,
      * Sequence number (if known), and sequence name.
      */
     String seqno = seq == -1 ? "" : " " + (seq + 1);
-    text.append("Sequence" + seqno + " ID: " + sequence.getName());
+    text.append("Sequence").append(seqno).append(" ID: ")
+            .append(sequence.getName());
 
     String residue = null;
     /*
@@ -856,8 +885,9 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
     else
     {
-      residue = "X".equalsIgnoreCase(displayChar) ? "X"
-              : ResidueProperties.aa2Triplet.get(displayChar);
+      residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
+              .equals(displayChar) ? "STOP" : ResidueProperties.aa2Triplet
+              .get(displayChar));
       if (residue != null)
       {
         text.append(" Residue: ").append(residue);
@@ -892,6 +922,10 @@ public class SeqPanel extends JPanel implements MouseListener,
     for (Match m : results.getResults())
     {
       SequenceI seq = m.getSequence();
+      if (seq.getDatasetSequence() != null)
+      {
+        seq = seq.getDatasetSequence();
+      }
 
       if (seq == ds)
       {
@@ -899,7 +933,7 @@ public class SeqPanel extends JPanel implements MouseListener,
          * Convert position in sequence (base 1) to sequence character array
          * index (base 0)
          */
-        int start = m.getStart() - 1;
+        int start = m.getStart() - m.getSequence().getStart();
         setStatusMessage(seq, start, sequenceIndex);
         return;
       }
@@ -1029,7 +1063,8 @@ public class SeqPanel extends JPanel implements MouseListener,
       message.append("Edit group:");
       if (editCommand == null)
       {
-        editCommand = new EditCommand(MessageManager.getString("action.edit_group"));
+        editCommand = new EditCommand(
+                MessageManager.getString("action.edit_group"));
       }
     }
     else
@@ -1042,7 +1077,8 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
       if (editCommand == null)
       {
-        editCommand = new EditCommand(MessageManager.formatMessage("label.edit_params", new String[]{label}));
+        editCommand = new EditCommand(MessageManager.formatMessage(
+                "label.edit_params", new String[] { label }));
       }
     }
 
@@ -1163,8 +1199,7 @@ public class SeqPanel extends JPanel implements MouseListener,
           {
             for (int j = 0; j < startres - lastres; j++)
             {
-              if (!jalview.util.Comparison.isGap(groupSeqs[g]
-                      .getCharAt(fixedRight - j)))
+              if (!Comparison.isGap(groupSeqs[g].getCharAt(fixedRight - j)))
               {
                 blank = false;
                 break;
@@ -1226,7 +1261,7 @@ public class SeqPanel extends JPanel implements MouseListener,
               continue;
             }
 
-            if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
+            if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
             {
               // Not a gap, block edit not valid
               endEditing();
@@ -1280,14 +1315,13 @@ public class SeqPanel extends JPanel implements MouseListener,
         {
           for (int j = lastres; j < startres; j++)
           {
-            insertChar(j, new SequenceI[]
-            { seq }, fixedRight);
+            insertChar(j, new SequenceI[] { seq }, fixedRight);
           }
         }
         else
         {
-          appendEdit(Action.INSERT_GAP, new SequenceI[]
-          { seq }, lastres, startres - lastres);
+          appendEdit(Action.INSERT_GAP, new SequenceI[] { seq }, lastres,
+                  startres - lastres);
         }
       }
       else
@@ -1304,8 +1338,7 @@ public class SeqPanel extends JPanel implements MouseListener,
                 endEditing();
                 break;
               }
-              deleteChar(startres, new SequenceI[]
-              { seq }, fixedRight);
+              deleteChar(startres, new SequenceI[] { seq }, fixedRight);
             }
           }
           else
@@ -1323,8 +1356,8 @@ public class SeqPanel extends JPanel implements MouseListener,
 
             if (max > 0)
             {
-              appendEdit(Action.DELETE_GAP, new SequenceI[]
-              { seq }, startres, max);
+              appendEdit(Action.DELETE_GAP, new SequenceI[] { seq },
+                      startres, max);
             }
           }
         }
@@ -1334,14 +1367,13 @@ public class SeqPanel extends JPanel implements MouseListener,
           {
             for (int j = lastres; j < startres; j++)
             {
-              insertChar(j, new SequenceI[]
-              { seq }, fixedRight);
+              insertChar(j, new SequenceI[] { seq }, fixedRight);
             }
           }
           else
           {
-            appendEdit(Action.INSERT_NUC, new SequenceI[]
-            { seq }, lastres, startres - lastres);
+            appendEdit(Action.INSERT_NUC, new SequenceI[] { seq }, lastres,
+                    startres - lastres);
           }
         }
       }
@@ -1361,7 +1393,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
       {
-        if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
+        if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
         {
           // Theres a space, so break and insert the gap
           break;
@@ -1397,8 +1429,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
             av.getAlignment().getGapCharacter());
 
-    editCommand.appendEdit(edit, av.getAlignment(),
-            true, null);
+    editCommand.appendEdit(edit, av.getAlignment(), true, null);
   }
 
   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
@@ -1464,21 +1495,23 @@ public class SeqPanel extends JPanel implements MouseListener,
         av.setSelectionGroup(null);
       }
 
-      List<SequenceFeature> features = seqCanvas.getFeatureRenderer().findFeaturesAtRes(
-              sequence.getDatasetSequence(),
-              sequence.findPosition(findRes(evt)));
+      List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
+              .findFeaturesAtRes(sequence.getDatasetSequence(),
+                      sequence.findPosition(findRes(evt)));
 
-      if (features != null && features.size()> 0)
+      if (features != null && features.size() > 0)
       {
         SearchResults highlight = new SearchResults();
-        highlight.addResult(sequence, features.get(0).getBegin(),
-                features.get(0).getEnd());
+        highlight.addResult(sequence, features.get(0).getBegin(), features
+                .get(0).getEnd());
         seqCanvas.highlightSearchResults(highlight);
       }
-      if (features != null && features.size()> 0)
+      if (features != null && features.size() > 0)
       {
-        seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
-        { sequence }, features.toArray(new SequenceFeature[features.size()]), false, ap);
+        seqCanvas.getFeatureRenderer().amendFeatures(
+                new SequenceI[] { sequence },
+                features.toArray(new SequenceFeature[features.size()]),
+                false, ap);
 
         seqCanvas.highlightSearchResults(null);
       }
@@ -1523,9 +1556,10 @@ public class SeqPanel extends JPanel implements MouseListener,
    */
   public void doMousePressedDefineMode(MouseEvent evt)
   {
-    int res = findRes(evt);
-    int seq = findSeq(evt);
+    final int res = findRes(evt);
+    final int seq = findSeq(evt);
     oldSeq = seq;
+    needOverviewUpdate = false;
 
     startWrapBlock = wrappedBlock;
 
@@ -1588,27 +1622,21 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
 
       av.setSelectionGroup(stretchGroup);
-
     }
 
-    if (javax.swing.SwingUtilities.isRightMouseButton(evt))
+    if (evt.isPopupTrigger()) // Mac: mousePressed
     {
-      List<SequenceFeature> allFeatures = ap.getFeatureRenderer().findFeaturesAtRes(
-              sequence.getDatasetSequence(), sequence.findPosition(res));
-      Vector links = new Vector();
-      for (SequenceFeature sf:allFeatures)
-      {
-        if (sf.links != null)
-        {
-          for (int j = 0; j < sf.links.size(); j++)
-          {
-            links.addElement(sf.links.elementAt(j));
-          }
-        }
-      }
+      showPopupMenu(evt);
+      return;
+    }
 
-      jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
-      pop.show(this, evt.getX(), evt.getY());
+    /*
+     * defer right-mouse click handling to mouseReleased on Windows
+     * (where isPopupTrigger() will answer true)
+     * NB isRightMouseButton is also true for Cmd-click on Mac
+     */
+    if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
+    {
       return;
     }
 
@@ -1630,7 +1658,6 @@ public class SeqPanel extends JPanel implements MouseListener,
       sg.setEndRes(res);
       sg.addSequence(sequence, false);
       av.setSelectionGroup(sg);
-
       stretchGroup = sg;
 
       if (av.getConservationSelected())
@@ -1662,6 +1689,37 @@ public class SeqPanel extends JPanel implements MouseListener,
   }
 
   /**
+   * Build and show a pop-up menu at the right-click mouse position
+   * 
+   * @param evt
+   * @param res
+   * @param sequence
+   */
+  void showPopupMenu(MouseEvent evt)
+  {
+    final int res = findRes(evt);
+    final int seq = findSeq(evt);
+    SequenceI sequence = av.getAlignment().getSequenceAt(seq);
+    List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
+            .findFeaturesAtRes(sequence.getDatasetSequence(),
+                    sequence.findPosition(res));
+    List<String> links = new ArrayList<String>();
+    for (SequenceFeature sf : allFeatures)
+    {
+      if (sf.links != null)
+      {
+        for (String link : sf.links)
+        {
+          links.add(link);
+        }
+      }
+    }
+
+    PopupMenu pop = new PopupMenu(ap, null, links);
+    pop.show(this, evt.getX(), evt.getY());
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @param evt
@@ -1673,9 +1731,10 @@ public class SeqPanel extends JPanel implements MouseListener,
     {
       return;
     }
-
-    stretchGroup.recalcConservation(); // always do this - annotation has own
-                                       // state
+    // always do this - annotation has own state
+    // but defer colourscheme update until hidden sequences are passed in
+    boolean vischange = stretchGroup.recalcConservation(true);
+    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup();
     if (stretchGroup.cs != null)
     {
       stretchGroup.cs.alignmentChanged(stretchGroup,
@@ -1693,8 +1752,8 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
     }
     PaintRefresher.Refresh(this, av.getSequenceSetId());
-    ap.paintAlignment(true);
-
+    ap.paintAlignment(needOverviewUpdate);
+    needOverviewUpdate = false;
     changeEndRes = false;
     changeStartRes = false;
     stretchGroup = null;
@@ -1748,6 +1807,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       if (res > (stretchGroup.getStartRes() - 1))
       {
         stretchGroup.setEndRes(res);
+        needOverviewUpdate |= av.isSelectionDefinedGroup();
       }
     }
     else if (changeStartRes)
@@ -1755,6 +1815,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       if (res < (stretchGroup.getEndRes() + 1))
       {
         stretchGroup.setStartRes(res);
+        needOverviewUpdate |= av.isSelectionDefinedGroup();
       }
     }
 
@@ -1788,6 +1849,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       if (stretchGroup.getSequences(null).contains(nextSeq))
       {
         stretchGroup.deleteSequence(seq, false);
+        needOverviewUpdate |= av.isSelectionDefinedGroup();
       }
       else
       {
@@ -1797,6 +1859,7 @@ public class SeqPanel extends JPanel implements MouseListener,
         }
 
         stretchGroup.addSequence(nextSeq, false);
+        needOverviewUpdate |= av.isSelectionDefinedGroup();
       }
     }
 
@@ -1912,8 +1975,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     // handles selection messages...
     // TODO: extend config options to allow user to control if selections may be
     // shared between viewports.
-    boolean iSentTheSelection = (av == source
-            || (source instanceof AlignViewport && ((AlignmentViewport) source)
+    boolean iSentTheSelection = (av == source || (source instanceof AlignViewport && ((AlignmentViewport) source)
             .getSequenceSetId().equals(av.getSequenceSetId())));
     if (iSentTheSelection || !av.followSelection)
     {
@@ -1939,28 +2001,28 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     // do we want to thread this ? (contention with seqsel and colsel locks, I
     // suspect)
-    // rules are: colsel is copied if there is a real intersection between
-    // sequence selection
+    /*
+     * only copy colsel if there is a real intersection between
+     * sequence selection and this panel's alignment
+     */
     boolean repaint = false;
-    boolean copycolsel = true;
+    boolean copycolsel = false;
 
     SequenceGroup sgroup = null;
     if (seqsel != null && seqsel.getSize() > 0)
     {
       if (av.getAlignment() == null)
       {
-        jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
-                + av.getSequenceSetId() + " ViewId=" + av.getViewId()
+        Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+                + " ViewId=" + av.getViewId()
                 + " 's alignment is NULL! returning immediately.");
         return;
       }
       sgroup = seqsel.intersect(av.getAlignment(),
               (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
-      if ((sgroup == null || sgroup.getSize() == 0)
-              || (colsel == null || colsel.size() == 0))
+      if ((sgroup != null && sgroup.getSize() > 0))
       {
-        // don't copy columns if the region didn't intersect.
-        copycolsel = false;
+        copycolsel = true;
       }
     }
     if (sgroup != null && sgroup.getSize() > 0)
@@ -1978,7 +2040,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     {
       // the current selection is unset or from a previous message
       // so import the new colsel.
-      if (colsel == null || colsel.size() == 0)
+      if (colsel == null || colsel.isEmpty())
       {
         if (av.getColumnSelection() != null)
         {
@@ -2029,11 +2091,13 @@ public class SeqPanel extends JPanel implements MouseListener,
   protected boolean selectionFromTranslation(SequenceGroup seqsel,
           ColumnSelection colsel, SelectionSource source)
   {
-    if (!(source instanceof AlignViewportI)) {
+    if (!(source instanceof AlignViewportI))
+    {
       return false;
     }
     final AlignViewportI sourceAv = (AlignViewportI) source;
-    if (sourceAv.getCodingComplement() != av && av.getCodingComplement() != sourceAv)
+    if (sourceAv.getCodingComplement() != av
+            && av.getCodingComplement() != sourceAv)
     {
       return false;
     }
@@ -2051,7 +2115,6 @@ public class SeqPanel extends JPanel implements MouseListener,
     ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
             av);
     av.setColumnSelection(cs);
-    av.isColSelChanged(true);
 
     PaintRefresher.Refresh(this, av.getSequenceSetId());