JAL-1645 Version-Rel Version 2.9 Year-Rel 2015 Licensing glob
[jalview.git] / src / jalview / gui / SeqPanel.java
index 1cc8715..e922a0d 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9)
+ * Copyright (C) 2015 The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  */
 package jalview.gui;
 
+import jalview.api.AlignViewportI;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
+import jalview.commands.EditCommand.Edit;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.SearchResults;
+import jalview.datamodel.SearchResults.Match;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
@@ -34,7 +38,11 @@ import jalview.structure.SelectionListener;
 import jalview.structure.SelectionSource;
 import jalview.structure.SequenceListener;
 import jalview.structure.StructureSelectionManager;
+import jalview.structure.VamsasSource;
+import jalview.util.Comparison;
+import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -121,6 +129,8 @@ public class SeqPanel extends JPanel implements MouseListener,
 
   StructureSelectionManager ssm;
 
+  SearchResults lastSearchResults;
+
   /**
    * Creates a new SeqPanel object.
    * 
@@ -160,22 +170,29 @@ public class SeqPanel extends JPanel implements MouseListener,
 
   int wrappedBlock = -1;
 
+  /**
+   * Returns the aligned sequence position (base 0) at the mouse position, or
+   * the closest visible one
+   * 
+   * @param evt
+   * @return
+   */
   int findRes(MouseEvent evt)
   {
     int res = 0;
     int x = evt.getX();
 
-    if (av.wrapAlignment)
+    if (av.getWrapAlignment())
     {
 
-      int hgap = av.charHeight;
-      if (av.scaleAboveWrapped)
+      int hgap = av.getCharHeight();
+      if (av.getScaleAboveWrapped())
       {
-        hgap += av.charHeight;
+        hgap += av.getCharHeight();
       }
 
-      int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
-              + seqCanvas.getAnnotationHeight();
+      int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
+              + hgap + seqCanvas.getAnnotationHeight();
 
       int y = evt.getY();
       y -= hgap;
@@ -195,13 +212,18 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
     else
     {
-      if (x > seqCanvas.getWidth() + seqCanvas.getWidth())
+      if (x > seqCanvas.getX() + seqCanvas.getWidth())
       {
         // make sure we calculate relative to visible alignment, rather than
         // right-hand gutter
         x = seqCanvas.getX() + seqCanvas.getWidth();
       }
       res = (x / av.getCharWidth()) + av.getStartRes();
+      if (res > av.getEndRes())
+      {
+        // moused off right
+        res = av.getEndRes();
+      }
     }
 
     if (av.hasHiddenColumns())
@@ -218,16 +240,16 @@ public class SeqPanel extends JPanel implements MouseListener,
     int seq = 0;
     int y = evt.getY();
 
-    if (av.wrapAlignment)
+    if (av.getWrapAlignment())
     {
-      int hgap = av.charHeight;
-      if (av.scaleAboveWrapped)
+      int hgap = av.getCharHeight();
+      if (av.getScaleAboveWrapped())
       {
-        hgap += av.charHeight;
+        hgap += av.getCharHeight();
       }
 
-      int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
-              + seqCanvas.getAnnotationHeight();
+      int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
+              + hgap + seqCanvas.getAnnotationHeight();
 
       y -= hgap;
 
@@ -243,22 +265,33 @@ public class SeqPanel extends JPanel implements MouseListener,
     return seq;
   }
 
+  /**
+   * When all of a sequence of edits are complete, put the resulting edit list
+   * on the history stack (undo list), and reset flags for editing in progress.
+   */
   void endEditing()
   {
-    if (editCommand != null && editCommand.getSize() > 0)
+    try
+    {
+      if (editCommand != null && editCommand.getSize() > 0)
+      {
+        ap.alignFrame.addHistoryItem(editCommand);
+        av.firePropertyChange("alignment", null, av.getAlignment()
+                .getSequences());
+      }
+    } finally
     {
-      ap.alignFrame.addHistoryItem(editCommand);
-      av.firePropertyChange("alignment", null, av.getAlignment()
-              .getSequences());
+      /*
+       * Tidy up come what may...
+       */
+      startseq = -1;
+      lastres = -1;
+      editingSeqs = false;
+      groupEditing = false;
+      keyboardNo1 = null;
+      keyboardNo2 = null;
+      editCommand = null;
     }
-
-    startseq = -1;
-    lastres = -1;
-    editingSeqs = false;
-    groupEditing = false;
-    keyboardNo1 = null;
-    keyboardNo2 = null;
-    editCommand = null;
   }
 
   void setCursorRow()
@@ -342,7 +375,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
 
     endEditing();
-    if (av.wrapAlignment)
+    if (av.getWrapAlignment())
     {
       ap.scrollToWrappedVisible(seqCanvas.cursorX);
     }
@@ -356,7 +389,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       {
         ap.scrollUp(false);
       }
-      if (!av.wrapAlignment)
+      if (!av.getWrapAlignment())
       {
         while (seqCanvas.cursorX < av.getColumnSelection()
                 .adjustForHiddenColumns(av.startRes))
@@ -624,20 +657,46 @@ public class SeqPanel extends JPanel implements MouseListener,
     lastMessage = tmp;
   }
 
+  /**
+   * Highlight the mapped region described by the search results object (unless
+   * unchanged). This supports highlight of protein while mousing over linked
+   * cDNA and vice versa. The status bar is also updated to show the location of
+   * the start of the highlighted region.
+   */
   @Override
   public void highlightSequence(SearchResults results)
   {
-    if (av.followHighlight)
+    if (results == null || results.equals(lastSearchResults))
     {
+      return;
+    }
+    lastSearchResults = results;
+
+    if (av.isFollowHighlight())
+    {
+      /*
+       * if scrollToPosition requires a scroll adjustment, this flag prevents
+       * another scroll event being propagated back to the originator
+       * 
+       * @see AlignmentPanel#adjustmentValueChanged
+       */
+      ap.setFollowingComplementScroll(true);
       if (ap.scrollToPosition(results, false))
       {
         seqCanvas.revalidate();
       }
     }
+    setStatusMessage(results);
     seqCanvas.highlightSearchResults(results);
   }
 
   @Override
+  public VamsasSource getVamsasSource()
+  {
+    return this.ap == null ? null : this.ap.av;
+  }
+
+  @Override
   public void updateColours(SequenceI seq, int index)
   {
     System.out.println("update the seqPanel colours");
@@ -708,9 +767,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());
     }
@@ -774,42 +833,85 @@ public class SeqPanel extends JPanel implements MouseListener,
    */
   int setStatusMessage(SequenceI sequence, int res, int seq)
   {
-    int pos = -1;
-    StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
-            + sequence.getName());
+    StringBuilder text = new StringBuilder(32);
+
+    /*
+     * Sequence number (if known), and sequence name.
+     */
+    String seqno = seq == -1 ? "" : " " + (seq + 1);
+    text.append("Sequence" + seqno + " ID: " + sequence.getName());
 
-    Object obj = null;
+    String residue = null;
+    /*
+     * Try to translate the display character to residue name (null for gap).
+     */
+    final String displayChar = String.valueOf(sequence.getCharAt(res));
     if (av.getAlignment().isNucleotide())
     {
-      obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
-              + "");
-      if (obj != null)
+      residue = ResidueProperties.nucleotideName.get(displayChar);
+      if (residue != null)
       {
-        text.append(" Nucleotide: ");
+        text.append(" Nucleotide: ").append(residue);
       }
     }
     else
     {
-      obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
-      if (obj != null)
+      residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
+              .equals(displayChar) ? "STOP" : ResidueProperties.aa2Triplet
+              .get(displayChar));
+      if (residue != null)
       {
-        text.append("  Residue: ");
+        text.append(" Residue: ").append(residue);
       }
     }
 
-    if (obj != null)
+    int pos = -1;
+    if (residue != null)
     {
       pos = sequence.findPosition(res);
-      if (obj != "")
-      {
-        text.append(obj + " (" + pos + ")");
-      }
+      text.append(" (").append(Integer.toString(pos)).append(")");
     }
     ap.alignFrame.statusBar.setText(text.toString());
     return pos;
   }
 
   /**
+   * Set the status bar message to highlight the first matched position in
+   * search results.
+   * 
+   * @param results
+   */
+  private void setStatusMessage(SearchResults results)
+  {
+    AlignmentI al = this.av.getAlignment();
+    int sequenceIndex = al.findIndex(results);
+    if (sequenceIndex == -1)
+    {
+      return;
+    }
+    SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
+    for (Match m : results.getResults())
+    {
+      SequenceI seq = m.getSequence();
+      if (seq.getDatasetSequence() != null)
+      {
+        seq = seq.getDatasetSequence();
+      }
+
+      if (seq == ds)
+      {
+        /*
+         * Convert position in sequence (base 1) to sequence character array
+         * index (base 0)
+         */
+        int start = m.getStart() - 1;
+        setStatusMessage(seq, start, sequenceIndex);
+        return;
+      }
+    }
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @param evt
@@ -820,7 +922,7 @@ public class SeqPanel extends JPanel implements MouseListener,
   {
     if (mouseWheelPressed)
     {
-      int oldWidth = av.charWidth;
+      int oldWidth = av.getCharWidth();
 
       // Which is bigger, left-right or up-down?
       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
@@ -842,26 +944,28 @@ public class SeqPanel extends JPanel implements MouseListener,
           fontSize = 1;
         }
 
-        av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
-        av.charWidth = oldWidth;
+        av.setFont(
+                new Font(av.font.getName(), av.font.getStyle(), fontSize),
+                true);
+        av.setCharWidth(oldWidth);
         ap.fontChanged();
       }
       else
       {
-        if (evt.getX() < lastMousePress.getX() && av.charWidth > 1)
+        if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
         {
-          av.charWidth--;
+          av.setCharWidth(av.getCharWidth() - 1);
         }
         else if (evt.getX() > lastMousePress.getX())
         {
-          av.charWidth++;
+          av.setCharWidth(av.getCharWidth() + 1);
         }
 
         ap.paintAlignment(false);
       }
 
       FontMetrics fm = getFontMetrics(av.getFont());
-      av.validCharWidth = fm.charWidth('M') <= av.charWidth;
+      av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
 
       lastMousePress = evt.getPoint();
 
@@ -924,13 +1028,14 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
     }
 
-    StringBuffer message = new StringBuffer();
+    StringBuilder message = new StringBuilder(64);
     if (groupEditing)
     {
       message.append("Edit group:");
       if (editCommand == null)
       {
-        editCommand = new EditCommand(MessageManager.getString("action.edit_group"));
+        editCommand = new EditCommand(
+                MessageManager.getString("action.edit_group"));
       }
     }
     else
@@ -943,7 +1048,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 }));
       }
     }
 
@@ -1149,8 +1255,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         }
         else
         {
-          editCommand.appendEdit(Action.INSERT_GAP, groupSeqs,
-                  startres, startres - lastres, av.getAlignment(), true);
+          appendEdit(Action.INSERT_GAP, groupSeqs, startres, startres
+                  - lastres);
         }
       }
       else
@@ -1165,8 +1271,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         }
         else
         {
-          editCommand.appendEdit(Action.DELETE_GAP, groupSeqs,
-                  startres, lastres - startres, av.getAlignment(), true);
+          appendEdit(Action.DELETE_GAP, groupSeqs, startres, lastres
+                  - startres);
         }
 
       }
@@ -1181,14 +1287,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
         {
-          editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[]
-          { seq }, lastres, startres - lastres, av.getAlignment(), true);
+          appendEdit(Action.INSERT_GAP, new SequenceI[] { seq }, lastres,
+                  startres - lastres);
         }
       }
       else
@@ -1200,13 +1305,12 @@ public class SeqPanel extends JPanel implements MouseListener,
           {
             for (int j = lastres; j > startres; j--)
             {
-              if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
+              if (!Comparison.isGap(seq.getCharAt(startres)))
               {
                 endEditing();
                 break;
               }
-              deleteChar(startres, new SequenceI[]
-              { seq }, fixedRight);
+              deleteChar(startres, new SequenceI[] { seq }, fixedRight);
             }
           }
           else
@@ -1215,7 +1319,7 @@ public class SeqPanel extends JPanel implements MouseListener,
             int max = 0;
             for (int m = startres; m < lastres; m++)
             {
-              if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
+              if (!Comparison.isGap(seq.getCharAt(m)))
               {
                 break;
               }
@@ -1224,9 +1328,8 @@ public class SeqPanel extends JPanel implements MouseListener,
 
             if (max > 0)
             {
-              editCommand.appendEdit(Action.DELETE_GAP,
-                      new SequenceI[]
-                      { seq }, startres, max, av.getAlignment(), true);
+              appendEdit(Action.DELETE_GAP, new SequenceI[] { seq },
+                      startres, max);
             }
           }
         }
@@ -1236,14 +1339,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
           {
-            editCommand.appendEdit(Action.INSERT_NUC, new SequenceI[]
-            { seq }, lastres, startres - lastres, av.getAlignment(), true);
+            appendEdit(Action.INSERT_NUC, new SequenceI[] { seq }, lastres,
+                    startres - lastres);
           }
         }
       }
@@ -1278,22 +1380,36 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
     }
 
-    editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.DELETE_GAP, seq, blankColumn, 1);
 
-    editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.INSERT_GAP, seq, j, 1);
 
   }
 
+  /**
+   * Helper method to add and perform one edit action.
+   * 
+   * @param action
+   * @param seq
+   * @param pos
+   * @param count
+   */
+  protected void appendEdit(Action action, SequenceI[] seq, int pos,
+          int count)
+  {
+
+    final Edit edit = new EditCommand().new Edit(action, seq, pos, count,
+            av.getAlignment().getGapCharacter());
+
+    editCommand.appendEdit(edit, av.getAlignment(), true, null);
+  }
+
   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
   {
 
-    editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.DELETE_GAP, seq, j, 1);
 
-    editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
-            av.getAlignment(), true);
+    appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1);
   }
 
   /**
@@ -1351,21 +1467,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);
       }
@@ -1416,7 +1534,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     startWrapBlock = wrappedBlock;
 
-    if (av.wrapAlignment && seq > av.getAlignment().getHeight())
+    if (av.getWrapAlignment() && seq > av.getAlignment().getHeight())
     {
       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
               .getString("label.cannot_edit_annotations_in_wrapped_view"),
@@ -1480,10 +1598,11 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
     {
-      List<SequenceFeature> allFeatures = ap.getFeatureRenderer().findFeaturesAtRes(
-              sequence.getDatasetSequence(), sequence.findPosition(res));
+      List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
+              .findFeaturesAtRes(sequence.getDatasetSequence(),
+                      sequence.findPosition(res));
       Vector links = new Vector();
-      for (SequenceFeature sf:allFeatures)
+      for (SequenceFeature sf : allFeatures)
       {
         if (sf.links != null)
         {
@@ -1799,52 +1918,67 @@ 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.
-    if (av == source
-            || !av.followSelection
-            || (av.isSelectionGroupChanged(false) || av
-                    .isColSelChanged(false))
-            || (source instanceof AlignViewport && ((AlignViewport) source)
-                    .getSequenceSetId().equals(av.getSequenceSetId())))
+    boolean iSentTheSelection = (av == source || (source instanceof AlignViewport && ((AlignmentViewport) source)
+            .getSequenceSetId().equals(av.getSequenceSetId())));
+    if (iSentTheSelection || !av.followSelection)
     {
       return;
     }
+
+    /*
+     * Ignore the selection if there is one of our own pending.
+     */
+    if (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
+    {
+      return;
+    }
+
+    /*
+     * Check for selection in a view of which this one is a dna/protein
+     * complement.
+     */
+    if (selectionFromTranslation(seqsel, colsel, source))
+    {
+      return;
+    }
+
     // 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
-    boolean repaint = false, copycolsel = true;
-    // if (!av.isSelectionGroupChanged(false))
+    boolean repaint = false;
+    boolean copycolsel = true;
+
+    SequenceGroup sgroup = null;
+    if (seqsel != null && seqsel.getSize() > 0)
     {
-      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()
-                  + " 's alignment is NULL! returning immediatly.");
-          return;
-        }
-        sgroup = seqsel.intersect(av.getAlignment(),
-                (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
-        if ((sgroup == null || sgroup.getSize() == 0)
-                || (colsel == null || colsel.size() == 0))
-        {
-          // don't copy columns if the region didn't intersect.
-          copycolsel = false;
-        }
-      }
-      if (sgroup != null && sgroup.getSize() > 0)
+      if (av.getAlignment() == null)
       {
-        av.setSelectionGroup(sgroup);
+        jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
+                + av.getSequenceSetId() + " ViewId=" + av.getViewId()
+                + " 's alignment is NULL! returning immediately.");
+        return;
       }
-      else
+      sgroup = seqsel.intersect(av.getAlignment(),
+              (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
+      if ((sgroup == null || sgroup.getSize() == 0)
+              || (colsel == null || colsel.size() == 0))
       {
-        av.setSelectionGroup(null);
+        // don't copy columns if the region didn't intersect.
+        copycolsel = false;
       }
-      av.isSelectionGroupChanged(true);
-      repaint = true;
     }
+    if (sgroup != null && sgroup.getSize() > 0)
+    {
+      av.setSelectionGroup(sgroup);
+    }
+    else
+    {
+      av.setSelectionGroup(null);
+    }
+    av.isSelectionGroupChanged(true);
+    repaint = true;
+
     if (copycolsel)
     {
       // the current selection is unset or from a previous message
@@ -1872,6 +2006,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       av.isColSelChanged(true);
       repaint = true;
     }
+
     if (copycolsel
             && av.hasHiddenColumns()
             && (av.getColumnSelection() == null || av.getColumnSelection()
@@ -1879,11 +2014,54 @@ public class SeqPanel extends JPanel implements MouseListener,
     {
       System.err.println("Bad things");
     }
-    if (repaint)
+    if (repaint) // always true!
     {
       // probably finessing with multiple redraws here
       PaintRefresher.Refresh(this, av.getSequenceSetId());
       // ap.paintAlignment(false);
     }
   }
+
+  /**
+   * If this panel is a cdna/protein translation view of the selection source,
+   * tries to map the source selection to a local one, and returns true. Else
+   * returns false.
+   * 
+   * @param seqsel
+   * @param colsel
+   * @param source
+   */
+  protected boolean selectionFromTranslation(SequenceGroup seqsel,
+          ColumnSelection colsel, SelectionSource source)
+  {
+    if (!(source instanceof AlignViewportI))
+    {
+      return false;
+    }
+    final AlignViewportI sourceAv = (AlignViewportI) source;
+    if (sourceAv.getCodingComplement() != av
+            && av.getCodingComplement() != sourceAv)
+    {
+      return false;
+    }
+
+    /*
+     * Map sequence selection
+     */
+    SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
+    av.setSelectionGroup(sg);
+    av.isSelectionGroupChanged(true);
+
+    /*
+     * Map column selection
+     */
+    ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
+            av);
+    av.setColumnSelection(cs);
+    av.isColSelChanged(true);
+
+    PaintRefresher.Refresh(this, av.getSequenceSetId());
+
+    return true;
+  }
 }