JAL-1807 still testing
[jalviewjs.git] / unused / appletgui / SeqPanel.java
index 9acde27..8c97899 100644 (file)
-/*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.appletgui;
-
-import jalview.api.AlignViewportI;
-import jalview.commands.EditCommand;
-import jalview.commands.EditCommand.Action;
-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;
-import jalview.datamodel.SequenceI;
-import jalview.schemes.ResidueProperties;
-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.Font;
-import java.awt.FontMetrics;
-import java.awt.Point;
-import java.awt.event.InputEvent;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-import java.util.List;
-import java.util.Vector;
-
-import javax.swing.JPanel;
-
-public class SeqPanel extends JPanel implements MouseMotionListener,
-        MouseListener, SequenceListener, SelectionListener
-{
-
-  public SeqCanvas seqCanvas;
-
-  public AlignmentPanel ap;
-
-  protected int lastres;
-
-  protected int startseq;
-
-  protected AlignViewport av;
-
-  // if character is inserted or deleted, we will need to recalculate the
-  // conservation
-  boolean seqEditOccurred = false;
-
-  ScrollThread scrollThread = null;
-
-  boolean mouseDragging = false;
-
-  boolean editingSeqs = false;
-
-  boolean groupEditing = false;
-
-  int oldSeq = -1;
-
-  boolean changeEndSeq = false;
-
-  boolean changeStartSeq = false;
-
-  boolean changeEndRes = false;
-
-  boolean changeStartRes = false;
-
-  SequenceGroup stretchGroup = null;
-
-  StringBuffer keyboardNo1;
-
-  StringBuffer keyboardNo2;
-
-  boolean mouseWheelPressed = false;
-
-  Point lastMousePress;
-
-  EditCommand editCommand;
-
-  StructureSelectionManager ssm;
-
-  public SeqPanel(AlignViewport avp, AlignmentPanel p)
-  {
-    this.av = avp;
-
-    seqCanvas = new SeqCanvas(avp);
-    setLayout(new BorderLayout());
-    add(seqCanvas);
-
-    ap = p;
-
-    seqCanvas.addMouseMotionListener(this);
-    seqCanvas.addMouseListener(this);
-    ssm = StructureSelectionManager.getStructureSelectionManager(av.applet);
-    ssm.addStructureViewerListener(this);
-    ssm.addSelectionListener(this);
-
-    seqCanvas.repaint();
-  }
-
-  void endEditing()
-  {
-    if (editCommand != null && editCommand.getSize() > 0)
-    {
-      ap.alignFrame.addHistoryItem(editCommand);
-      av.firePropertyChange("alignment", null, av.getAlignment()
-              .getSequences());
-    }
-
-    startseq = -1;
-    lastres = -1;
-    editingSeqs = false;
-    groupEditing = false;
-    keyboardNo1 = null;
-    keyboardNo2 = null;
-    editCommand = null;
-  }
-
-  void setCursorRow()
-  {
-    seqCanvas.cursorY = getKeyboardNo1() - 1;
-    scrollToVisible();
-  }
-
-  void setCursorColumn()
-  {
-    seqCanvas.cursorX = getKeyboardNo1() - 1;
-    scrollToVisible();
-  }
-
-  void setCursorRowAndColumn()
-  {
-    if (keyboardNo2 == null)
-    {
-      keyboardNo2 = new StringBuffer();
-    }
-    else
-    {
-      seqCanvas.cursorX = getKeyboardNo1() - 1;
-      seqCanvas.cursorY = getKeyboardNo2() - 1;
-      scrollToVisible();
-    }
-  }
-
-  void setCursorPosition()
-  {
-    SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
-
-    seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
-    scrollToVisible();
-  }
-
-  void moveCursor(int dx, int dy)
-  {
-    seqCanvas.cursorX += dx;
-    seqCanvas.cursorY += dy;
-    if (av.hasHiddenColumns()
-            && !av.getColumnSelection().isVisible(seqCanvas.cursorX))
-    {
-      int original = seqCanvas.cursorX - dx;
-      int maxWidth = av.getAlignment().getWidth();
-
-      while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)
-              && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
-      {
-        seqCanvas.cursorX += dx;
-      }
-
-      if (seqCanvas.cursorX >= maxWidth
-              || !av.getColumnSelection().isVisible(seqCanvas.cursorX))
-      {
-        seqCanvas.cursorX = original;
-      }
-    }
-    scrollToVisible();
-  }
-
-  void scrollToVisible()
-  {
-    if (seqCanvas.cursorX < 0)
-    {
-      seqCanvas.cursorX = 0;
-    }
-    else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)
-    {
-      seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
-    }
-
-    if (seqCanvas.cursorY < 0)
-    {
-      seqCanvas.cursorY = 0;
-    }
-    else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
-    {
-      seqCanvas.cursorY = av.getAlignment().getHeight() - 1;
-    }
-
-    endEditing();
-    if (av.getWrapAlignment())
-    {
-      ap.scrollToWrappedVisible(seqCanvas.cursorX);
-    }
-    else
-    {
-      while (seqCanvas.cursorY < av.startSeq)
-      {
-        ap.scrollUp(true);
-      }
-      while (seqCanvas.cursorY + 1 > av.endSeq)
-      {
-        ap.scrollUp(false);
-      }
-      while (seqCanvas.cursorX < av.getColumnSelection()
-              .adjustForHiddenColumns(av.startRes))
-      {
-
-        if (!ap.scrollRight(false))
-        {
-          break;
-        }
-      }
-      while (seqCanvas.cursorX > av.getColumnSelection()
-              .adjustForHiddenColumns(av.endRes))
-      {
-        if (!ap.scrollRight(true))
-        {
-          break;
-        }
-      }
-    }
-    setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
-            seqCanvas.cursorX, seqCanvas.cursorY);
-
-    seqCanvas.repaint();
-  }
-
-  void setSelectionAreaAtCursor(boolean topLeft)
-  {
-    SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
-
-    if (av.getSelectionGroup() != null)
-    {
-      SequenceGroup sg = av.getSelectionGroup();
-      // Find the top and bottom of this group
-      int min = av.getAlignment().getHeight(), max = 0;
-      for (int i = 0; i < sg.getSize(); i++)
-      {
-        int index = av.getAlignment().findIndex(sg.getSequenceAt(i));
-        if (index > max)
-        {
-          max = index;
-        }
-        if (index < min)
-        {
-          min = index;
-        }
-      }
-
-      max++;
-
-      if (topLeft)
-      {
-        sg.setStartRes(seqCanvas.cursorX);
-        if (sg.getEndRes() < seqCanvas.cursorX)
-        {
-          sg.setEndRes(seqCanvas.cursorX);
-        }
-
-        min = seqCanvas.cursorY;
-      }
-      else
-      {
-        sg.setEndRes(seqCanvas.cursorX);
-        if (sg.getStartRes() > seqCanvas.cursorX)
-        {
-          sg.setStartRes(seqCanvas.cursorX);
-        }
-
-        max = seqCanvas.cursorY + 1;
-      }
-
-      if (min > max)
-      {
-        // Only the user can do this
-        av.setSelectionGroup(null);
-      }
-      else
-      {
-        // Now add any sequences between min and max
-        sg.clear();
-        for (int i = min; i < max; i++)
-        {
-          sg.addSequence(av.getAlignment().getSequenceAt(i), false);
-        }
-      }
-    }
-
-    if (av.getSelectionGroup() == null)
-    {
-      SequenceGroup sg = new SequenceGroup();
-      sg.setStartRes(seqCanvas.cursorX);
-      sg.setEndRes(seqCanvas.cursorX);
-      sg.addSequence(sequence, false);
-      av.setSelectionGroup(sg);
-    }
-    ap.paintAlignment(false);
-    av.sendSelection();
-  }
-
-  void insertGapAtCursor(boolean group)
-  {
-    groupEditing = group;
-    startseq = seqCanvas.cursorY;
-    lastres = seqCanvas.cursorX;
-    editSequence(true, seqCanvas.cursorX + getKeyboardNo1());
-    endEditing();
-  }
-
-  void deleteGapAtCursor(boolean group)
-  {
-    groupEditing = group;
-    startseq = seqCanvas.cursorY;
-    lastres = seqCanvas.cursorX + getKeyboardNo1();
-    editSequence(false, seqCanvas.cursorX);
-    endEditing();
-  }
-
-  void numberPressed(char value)
-  {
-    if (keyboardNo1 == null)
-    {
-      keyboardNo1 = new StringBuffer();
-    }
-
-    if (keyboardNo2 != null)
-    {
-      keyboardNo2.append(value);
-    }
-    else
-    {
-      keyboardNo1.append(value);
-    }
-  }
-
-  int getKeyboardNo1()
-  {
-    try
-    {
-      if (keyboardNo1 != null)
-      {
-        int value = Integer.parseInt(keyboardNo1.toString());
-        keyboardNo1 = null;
-        return value;
-      }
-    } catch (Exception x)
-    {
-    }
-    keyboardNo1 = null;
-    return 1;
-  }
-
-  int getKeyboardNo2()
-  {
-    try
-    {
-      if (keyboardNo2 != null)
-      {
-        int value = Integer.parseInt(keyboardNo2.toString());
-        keyboardNo2 = null;
-        return value;
-      }
-    } catch (Exception x)
-    {
-    }
-    keyboardNo2 = null;
-    return 1;
-  }
-
-  /**
-   * Set status message in alignment panel
-   * 
-   * @param sequence
-   *          aligned sequence object
-   * @param res
-   *          alignment column
-   * @param seq
-   *          index of sequence in alignment
-   * @return position of res in sequence
-   */
-  void setStatusMessage(SequenceI sequence, int res, int seq)
-  {
-    // TODO remove duplication of identical gui method
-    StringBuilder text = new StringBuilder(32);
-    String seqno = seq == -1 ? "" : " " + (seq + 1);
-    text.append("Sequence" + seqno + " ID: " + sequence.getName());
-
-    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())
-    {
-      residue = ResidueProperties.nucleotideName.get(displayChar);
-      if (residue != null)
-      {
-        text.append(" Nucleotide: ").append(residue);
-      }
-    }
-    else
-    {
-      residue = "X".equalsIgnoreCase(displayChar) ? "X"
-              : ResidueProperties.aa2Triplet.get(displayChar);
-      if (residue != null)
-      {
-        text.append(" Residue: ").append(residue);
-      }
-    }
-
-    int pos = -1;
-    if (residue != null)
-    {
-      pos = sequence.findPosition(res);
-      text.append(" (").append(Integer.toString(pos)).append(")");
-    }
-    // Object obj = null;
-    // if (av.getAlignment().isNucleotide())
-    // {
-    // obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
-    // + "");
-    // if (obj != null)
-    // {
-    // text.append(" Nucleotide: ");
-    // }
-    // }
-    // else
-    // {
-    // obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
-    // if (obj != null)
-    // {
-    // text.append("  Residue: ");
-    // }
-    // }
-    //
-    // if (obj != null)
-    // {
-    //
-    // if (obj != "")
-    // {
-    // text.append(obj + " (" + sequence.findPosition(res) + ")");
-    // }
-    // }
-
-    ap.alignFrame.statusBar.setText(text.toString());
-
-  }
-
-  /**
-   * Set the status bar message to highlight the first matched position in
-   * search results.
-   * 
-   * @param results
-   */
-  private void setStatusMessage(SearchResults results)
-  {
-    List<Match> matches = results.getResults();
-    if (!matches.isEmpty())
-    {
-      Match m = matches.get(0);
-      SequenceI seq = m.getSequence();
-      int sequenceIndex = this.av.getAlignment().findIndex(seq);
-
-      /*
-       * Convert position in sequence (base 1) to sequence character array index
-       * (base 0)
-       */
-      int start = m.getStart() - 1;
-      setStatusMessage(seq, start, sequenceIndex);
-    }
-  }
-
-  public void mousePressed(MouseEvent evt)
-  {
-    lastMousePress = evt.getPoint();
-
-    // For now, ignore the mouseWheel font resizing on Macs
-    // As the Button2_mask always seems to be true
-    if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK
-            && !av.MAC)
-    {
-      mouseWheelPressed = true;
-      return;
-    }
-
-    if (evt.isShiftDown() || evt.isControlDown() || evt.isAltDown())
-    {
-      if (evt.isControlDown() || evt.isAltDown())
-      {
-        groupEditing = true;
-      }
-      editingSeqs = true;
-    }
-    else
-    {
-      doMousePressedDefineMode(evt);
-      return;
-    }
-
-    int seq = findSeq(evt);
-    int res = findRes(evt);
-
-    if (seq < 0 || res < 0)
-    {
-      return;
-    }
-
-    if ((seq < av.getAlignment().getHeight())
-            && (res < av.getAlignment().getSequenceAt(seq).getLength()))
-    {
-      startseq = seq;
-      lastres = res;
-    }
-    else
-    {
-      startseq = -1;
-      lastres = -1;
-    }
-
-    return;
-  }
-
-  public void mouseClicked(MouseEvent evt)
-  {
-    SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
-    if (evt.getClickCount() > 1)
-    {
-      if (av.getSelectionGroup() != null
-              && av.getSelectionGroup().getSize() == 1
-              && av.getSelectionGroup().getEndRes()
-                      - av.getSelectionGroup().getStartRes() < 2)
-      {
-        av.setSelectionGroup(null);
-      }
-
-      SequenceFeature[] features = findFeaturesAtRes(sequence,
-              sequence.findPosition(findRes(evt)));
-
-      if (features != null && features.length > 0)
-      {
-        SearchResults highlight = new SearchResults();
-        highlight.addResult(sequence, features[0].getBegin(),
-                features[0].getEnd());
-        seqCanvas.highlightSearchResults(highlight);
-      }
-      if (features != null && features.length > 0)
-      {
-        seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
-        { sequence }, features, false, ap);
-
-        seqCanvas.highlightSearchResults(null);
-      }
-    }
-  }
-
-  public void mouseReleased(MouseEvent evt)
-  {
-    mouseDragging = false;
-    mouseWheelPressed = false;
-    ap.paintAlignment(true);
-
-    if (!editingSeqs)
-    {
-      doMouseReleasedDefineMode(evt);
-      return;
-    }
-
-    endEditing();
-
-  }
-
-  int startWrapBlock = -1;
-
-  int wrappedBlock = -1;
-
-  int findRes(MouseEvent evt)
-  {
-    int res = 0;
-    int x = evt.getX();
-
-    if (av.getWrapAlignment())
-    {
-
-      int hgap = av.getCharHeight();
-      if (av.getScaleAboveWrapped())
-      {
-        hgap += av.getCharHeight();
-      }
-
-      int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
-              + hgap
-              + seqCanvas.getAnnotationHeight();
-
-      int y = evt.getY();
-      y -= hgap;
-      x -= seqCanvas.LABEL_WEST;
-
-      int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);
-      if (cwidth < 1)
-      {
-        return 0;
-      }
-
-      wrappedBlock = y / cHeight;
-      wrappedBlock += av.getStartRes() / cwidth;
-
-      res = wrappedBlock * cwidth + x / av.getCharWidth();
-
-    }
-    else
-    {
-      res = (x / av.getCharWidth()) + av.getStartRes();
-    }
-
-    if (av.hasHiddenColumns())
-    {
-      res = av.getColumnSelection().adjustForHiddenColumns(res);
-    }
-
-    return res;
-
-  }
-
-  int findSeq(MouseEvent evt)
-  {
-    final int sqnum = findAlRow(evt);
-    return (sqnum < 0) ? 0 : sqnum;
-  }
-
-  /**
-   * 
-   * @param evt
-   * @return row in alignment that was selected (or -1 for column selection)
-   */
-  private int findAlRow(MouseEvent evt)
-  {
-    int seq = 0;
-    int y = evt.getY();
-
-    if (av.getWrapAlignment())
-    {
-      int hgap = av.getCharHeight();
-      if (av.getScaleAboveWrapped())
-      {
-        hgap += av.getCharHeight();
-      }
-
-      int cHeight = av.getAlignment().getHeight() * av.getCharHeight()
-              + hgap
-              + seqCanvas.getAnnotationHeight();
-
-      y -= hgap;
-
-      seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
-              .getHeight() - 1);
-      if (seq < 0)
-      {
-        seq = -1;
-      }
-    }
-    else
-    {
-      seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
-              .getAlignment().getHeight() - 1);
-      if (seq < 0)
-      {
-        seq = -1;
-      }
-    }
-
-    return seq;
-  }
-
-  public void doMousePressed(MouseEvent evt)
-  {
-
-    int seq = findSeq(evt);
-    int res = findRes(evt);
-
-    if (seq < av.getAlignment().getHeight()
-            && res < av.getAlignment().getSequenceAt(seq).getLength())
-    {
-      // char resstr = align.getSequenceAt(seq).getSequence().charAt(res);
-      // Find the residue's position in the sequence (res is the position
-      // in the alignment
-
-      startseq = seq;
-      lastres = res;
-    }
-    else
-    {
-      startseq = -1;
-      lastres = -1;
-    }
-
-    return;
-  }
-
-  String lastMessage;
-
-  public void mouseOverSequence(SequenceI sequence, int index, int pos)
-  {
-    String tmp = sequence.hashCode() + index + "";
-    if (lastMessage == null || !lastMessage.equals(tmp))
-    {
-      ssm.mouseOverSequence(sequence, index, pos, av);
-    }
-
-    lastMessage = tmp;
-  }
-
-  public void highlightSequence(SearchResults results)
-  {
-    if (av.isFollowHighlight())
-    {
-      if (ap.scrollToPosition(results, true))
-      {
-        ap.alignFrame.repaint();
-      }
-    }
-    setStatusMessage(results);
-    seqCanvas.highlightSearchResults(results);
-
-  }
-
-  @Override
-  public VamsasSource getVamsasSource()
-  {
-    return this.ap == null ? null : this.ap.av;
-  }
-
-  public void updateColours(SequenceI seq, int index)
-  {
-    System.out.println("update the seqPanel colours");
-    // repaint();
-  }
-
-  public void mouseMoved(MouseEvent evt)
-  {
-    int res = findRes(evt);
-    int seq = findSeq(evt);
-
-    if (seq >= av.getAlignment().getHeight() || seq < 0 || res < 0)
-    {
-      if (tooltip != null)
-      {
-        tooltip.setTip("");
-      }
-      return;
-    }
-
-    SequenceI sequence = av.getAlignment().getSequenceAt(seq);
-    if (res > sequence.getLength())
-    {
-      if (tooltip != null)
-      {
-        tooltip.setTip("");
-      }
-      return;
-    }
-
-    int respos = sequence.findPosition(res);
-    if (ssm != null)
-    {
-      mouseOverSequence(sequence, res, respos);
-    }
-
-    StringBuilder text = new StringBuilder();
-    text.append("Sequence ").append(Integer.toString(seq + 1))
-            .append(" ID: ").append(sequence.getName());
-
-    String obj = null;
-    final String ch = String.valueOf(sequence.getCharAt(res));
-    if (av.getAlignment().isNucleotide())
-    {
-      obj = ResidueProperties.nucleotideName.get(ch);
-      if (obj != null)
-      {
-        text.append(" Nucleotide: ").append(obj);
-      }
-    }
-    else
-    {
-      obj = "X".equalsIgnoreCase(ch) ? "X"
-              : ResidueProperties.aa2Triplet.get(ch);
-      if (obj != null)
-      {
-        text.append(" Residue: ").append(obj);
-      }
-    }
-
-    if (obj != null)
-    {
-      text.append(" (").append(Integer.toString(respos)).append(")");
-    }
-
-    ap.alignFrame.statusBar.setText(text.toString());
-
-    StringBuilder tooltipText = new StringBuilder();
-    SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
-    if (groups != null)
-    {
-      for (int g = 0; g < groups.length; g++)
-      {
-        if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
-        {
-          if (!groups[g].getName().startsWith("JTreeGroup")
-                  && !groups[g].getName().startsWith("JGroup"))
-          {
-            tooltipText.append(groups[g].getName()).append(" ");
-          }
-          if (groups[g].getDescription() != null)
-          {
-            tooltipText.append(groups[g].getDescription());
-          }
-          tooltipText.append("\n");
-        }
-      }
-    }
-
-    // use aa to see if the mouse pointer is on a
-    SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
-            sequence.findPosition(res));
-
-    int index = 0;
-    while (index < allFeatures.length)
-    {
-      SequenceFeature sf = allFeatures[index];
-
-      tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
-
-      if (sf.getDescription() != null)
-      {
-        tooltipText.append(" " + sf.getDescription());
-      }
-
-      if (sf.getValue("status") != null)
-      {
-        String status = sf.getValue("status").toString();
-        if (status.length() > 0)
-        {
-          tooltipText.append(" (" + sf.getValue("status") + ")");
-        }
-      }
-      tooltipText.append("\n");
-
-      index++;
-    }
-
-    if (tooltip == null)
-    {
-      tooltip = new Tooltip(tooltipText.toString(), seqCanvas);
-    }
-    else
-    {
-      tooltip.setTip(tooltipText.toString());
-    }
-  }
-
-  SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
-  {
-    Vector tmp = new Vector();
-    SequenceFeature[] features = sequence.getSequenceFeatures();
-    if (features != null)
-    {
-      for (int i = 0; i < features.length; i++)
-      {
-        if (av.getFeaturesDisplayed() == null
-                || !av.getFeaturesDisplayed().isVisible(features[i].getType()))
-        {
-          continue;
-        }
-
-        if (features[i].featureGroup != null
-                && !seqCanvas.fr.checkGroupVisibility(features[i].featureGroup,false))
-        {
-          continue;
-        }
-
-        if ((features[i].getBegin() <= res)
-                && (features[i].getEnd() >= res))
-        {
-          tmp.addElement(features[i]);
-        }
-      }
-    }
-
-    features = new SequenceFeature[tmp.size()];
-    tmp.copyInto(features);
-
-    return features;
-  }
-
-  Tooltip tooltip;
-
-  public void mouseDragged(MouseEvent evt)
-  {
-    if (mouseWheelPressed)
-    {
-      int oldWidth = av.getCharWidth();
-
-      // Which is bigger, left-right or up-down?
-      if (Math.abs(evt.getY() - lastMousePress.y) > Math.abs(evt.getX()
-              - lastMousePress.x))
-      {
-        int fontSize = av.font.getSize();
-
-        if (evt.getY() < lastMousePress.y && av.getCharHeight() > 1)
-        {
-          fontSize--;
-        }
-        else if (evt.getY() > lastMousePress.y)
-        {
-          fontSize++;
-        }
-
-        if (fontSize < 1)
-        {
-          fontSize = 1;
-        }
-
-        av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
-        av.setCharWidth(oldWidth);
-      }
-      else
-      {
-        if (evt.getX() < lastMousePress.x && av.getCharWidth() > 1)
-        {
-          av.setCharWidth(av.getCharWidth() - 1);
-        }
-        else if (evt.getX() > lastMousePress.x)
-        {
-          av.setCharWidth(av.getCharWidth() + 1);
-        }
-
-        if (av.getCharWidth() < 1)
-        {
-          av.setCharWidth(1);
-        }
-      }
-
-      ap.fontChanged();
-
-      FontMetrics fm = getFontMetrics(av.getFont());
-      av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
-
-      lastMousePress = evt.getPoint();
-
-      ap.paintAlignment(false);
-      ap.annotationPanel.image = null;
-      return;
-    }
-
-    if (!editingSeqs)
-    {
-      doMouseDraggedDefineMode(evt);
-      return;
-    }
-
-    int res = findRes(evt);
-
-    if (res < 0)
-    {
-      res = 0;
-    }
-
-    if ((lastres == -1) || (lastres == res))
-    {
-      return;
-    }
-
-    if ((res < av.getAlignment().getWidth()) && (res < lastres))
-    {
-      // dragLeft, delete gap
-      editSequence(false, res);
-    }
-    else
-    {
-      editSequence(true, res);
-    }
-
-    mouseDragging = true;
-    if (scrollThread != null)
-    {
-      scrollThread.setEvent(evt);
-    }
-
-  }
-
-  synchronized void editSequence(boolean insertGap, int startres)
-  {
-    int fixedLeft = -1;
-    int fixedRight = -1;
-    boolean fixedColumns = false;
-    SequenceGroup sg = av.getSelectionGroup();
-
-    SequenceI seq = av.getAlignment().getSequenceAt(startseq);
-
-    if (!groupEditing && av.hasHiddenRows())
-    {
-      if (av.isHiddenRepSequence(seq))
-      {
-        sg = av.getRepresentedSequences(seq);
-        groupEditing = true;
-      }
-    }
-
-    StringBuffer message = new StringBuffer();
-    if (groupEditing)
-    {
-      message.append(MessageManager.getString("action.edit_group")).append(
-              ":");
-      if (editCommand == null)
-      {
-        editCommand = new EditCommand(
-                MessageManager.getString("action.edit_group"));
-      }
-    }
-    else
-    {
-      message.append(MessageManager.getString("label.edit_sequence"))
-              .append(" " + seq.getName());
-      String label = seq.getName();
-      if (label.length() > 10)
-      {
-        label = label.substring(0, 10);
-      }
-      if (editCommand == null)
-      {
-        editCommand = new EditCommand(MessageManager.formatMessage(
-                "label.edit_params", new String[]
-                { label }));
-      }
-    }
-
-    if (insertGap)
-    {
-      message.append(" insert ");
-    }
-    else
-    {
-      message.append(" delete ");
-    }
-
-    message.append(Math.abs(startres - lastres) + " gaps.");
-    ap.alignFrame.statusBar.setText(message.toString());
-
-    // Are we editing within a selection group?
-    if (groupEditing
-            || (sg != null && sg.getSequences(av.getHiddenRepSequences())
-                    .contains(seq)))
-    {
-      fixedColumns = true;
-
-      // sg might be null as the user may only see 1 sequence,
-      // but the sequence represents a group
-      if (sg == null)
-      {
-        if (!av.isHiddenRepSequence(seq))
-        {
-          endEditing();
-          return;
-        }
-
-        sg = av.getRepresentedSequences(seq);
-      }
-
-      fixedLeft = sg.getStartRes();
-      fixedRight = sg.getEndRes();
-
-      if ((startres < fixedLeft && lastres >= fixedLeft)
-              || (startres >= fixedLeft && lastres < fixedLeft)
-              || (startres > fixedRight && lastres <= fixedRight)
-              || (startres <= fixedRight && lastres > fixedRight))
-      {
-        endEditing();
-        return;
-      }
-
-      if (fixedLeft > startres)
-      {
-        fixedRight = fixedLeft - 1;
-        fixedLeft = 0;
-      }
-      else if (fixedRight < startres)
-      {
-        fixedLeft = fixedRight;
-        fixedRight = -1;
-      }
-    }
-
-    if (av.hasHiddenColumns())
-    {
-      fixedColumns = true;
-      int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
-      int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
-
-      if ((insertGap && startres > y1 && lastres < y1)
-              || (!insertGap && startres < y2 && lastres > y2))
-      {
-        endEditing();
-        return;
-      }
-
-      // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
-      // Selection spans a hidden region
-      if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
-      {
-        if (startres >= y2)
-        {
-          fixedLeft = y2;
-        }
-        else
-        {
-          fixedRight = y2 - 1;
-        }
-      }
-    }
-
-    if (groupEditing)
-    {
-      SequenceI[] groupSeqs = sg.getSequences(av.getHiddenRepSequences())
-              .toArray(new SequenceI[0]);
-
-      // drag to right
-      if (insertGap)
-      {
-        // If the user has selected the whole sequence, and is dragging to
-        // the right, we can still extend the alignment and selectionGroup
-        if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
-                && sg.getEndRes() == av.getAlignment().getWidth() - 1)
-        {
-          sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
-          fixedRight = sg.getEndRes();
-        }
-
-        // Is it valid with fixed columns??
-        // Find the next gap before the end
-        // of the visible region boundary
-        boolean blank = false;
-        for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
-        {
-          blank = true;
-
-          for (SequenceI gs : groupSeqs)
-          {
-            for (int j = 0; j < startres - lastres; j++)
-            {
-              if (!Comparison.isGap(gs.getCharAt(fixedRight
-                      - j)))
-              {
-                blank = false;
-                break;
-              }
-            }
-          }
-          if (blank)
-          {
-            break;
-          }
-        }
-
-        if (!blank)
-        {
-          if (sg.getSize() == av.getAlignment().getHeight())
-          {
-            if ((av.hasHiddenColumns() && startres < av
-                    .getColumnSelection().getHiddenBoundaryRight(startres)))
-            {
-              endEditing();
-              return;
-            }
-
-            int alWidth = av.getAlignment().getWidth();
-            if (av.hasHiddenRows())
-            {
-              int hwidth = av.getAlignment().getHiddenSequences()
-                      .getWidth();
-              if (hwidth > alWidth)
-              {
-                alWidth = hwidth;
-              }
-            }
-            // We can still insert gaps if the selectionGroup
-            // contains all the sequences
-            sg.setEndRes(sg.getEndRes() + startres - lastres);
-            fixedRight = alWidth + startres - lastres;
-          }
-          else
-          {
-            endEditing();
-            return;
-          }
-        }
-      }
-
-      // drag to left
-      else if (!insertGap)
-      {
-        // / Are we able to delete?
-        // ie are all columns blank?
-
-        for (SequenceI gs : groupSeqs)
-        {
-          for (int j = startres; j < lastres; j++)
-          {
-            if (gs.getLength() <= j)
-            {
-              continue;
-            }
-
-            if (!Comparison.isGap(gs.getCharAt(j)))
-            {
-              // Not a gap, block edit not valid
-              endEditing();
-              return;
-            }
-          }
-        }
-      }
-
-      if (insertGap)
-      {
-        // dragging to the right
-        if (fixedColumns && fixedRight != -1)
-        {
-          for (int j = lastres; j < startres; j++)
-          {
-            insertChar(j, groupSeqs, fixedRight);
-          }
-        }
-        else
-        {
-          editCommand.appendEdit(Action.INSERT_GAP, groupSeqs, startres,
-                  startres - lastres, av.getAlignment(), true);
-        }
-      }
-      else
-      {
-        // dragging to the left
-        if (fixedColumns && fixedRight != -1)
-        {
-          for (int j = lastres; j > startres; j--)
-          {
-            deleteChar(startres, groupSeqs, fixedRight);
-          }
-        }
-        else
-        {
-          editCommand.appendEdit(Action.DELETE_GAP, groupSeqs, startres,
-                  lastres - startres, av.getAlignment(), true);
-        }
-
-      }
-    }
-    else
-    // ///Editing a single sequence///////////
-    {
-      if (insertGap)
-      {
-        // dragging to the right
-        if (fixedColumns && fixedRight != -1)
-        {
-          for (int j = lastres; j < startres; j++)
-          {
-            insertChar(j, new SequenceI[]
-            { seq }, fixedRight);
-          }
-        }
-        else
-        {
-          editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[]
-          { seq }, lastres, startres - lastres, av.getAlignment(), true);
-        }
-      }
-      else
-      {
-        // dragging to the left
-        if (fixedColumns && fixedRight != -1)
-        {
-          for (int j = lastres; j > startres; j--)
-          {
-            if (!Comparison.isGap(seq.getCharAt(startres)))
-            {
-              endEditing();
-              break;
-            }
-            deleteChar(startres, new SequenceI[]
-            { seq }, fixedRight);
-          }
-        }
-        else
-        {
-          // could be a keyboard edit trying to delete none gaps
-          int max = 0;
-          for (int m = startres; m < lastres; m++)
-          {
-            if (!Comparison.isGap(seq.getCharAt(m)))
-            {
-              break;
-            }
-            max++;
-          }
-
-          if (max > 0)
-          {
-            editCommand.appendEdit(Action.DELETE_GAP, new SequenceI[]
-            { seq }, startres, max, av.getAlignment(), true);
-          }
-        }
-      }
-    }
-
-    lastres = startres;
-    seqCanvas.repaint();
-  }
-
-  void insertChar(int j, SequenceI[] seq, int fixedColumn)
-  {
-    int blankColumn = fixedColumn;
-    for (int s = 0; s < seq.length; s++)
-    {
-      // Find the next gap before the end of the visible region boundary
-      // If lastCol > j, theres a boundary after the gap insertion
-
-      for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
-      {
-        if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
-        {
-          // Theres a space, so break and insert the gap
-          break;
-        }
-      }
-
-      if (blankColumn <= j)
-      {
-        blankColumn = fixedColumn;
-        endEditing();
-        return;
-      }
-    }
-
-    editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
-            av.getAlignment(), true);
-
-    editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1, av.getAlignment(),
-            true);
-
-  }
-
-  void deleteChar(int j, SequenceI[] seq, int fixedColumn)
-  {
-
-    editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1, av.getAlignment(),
-            true);
-
-    editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
-            av.getAlignment(), true);
-  }
-
-  // ////////////////////////////////////////
-  // ///Everything below this is for defining the boundary of the rubberband
-  // ////////////////////////////////////////
-  public void doMousePressedDefineMode(MouseEvent evt)
-  {
-    if (scrollThread != null)
-    {
-      scrollThread.running = false;
-      scrollThread = null;
-    }
-
-    int res = findRes(evt);
-    int seq = findSeq(evt);
-    oldSeq = seq;
-    startWrapBlock = wrappedBlock;
-
-    if (seq == -1)
-    {
-      return;
-    }
-
-    SequenceI sequence = av.getAlignment().getSequenceAt(seq);
-
-    if (sequence == null || res > sequence.getLength())
-    {
-      return;
-    }
-
-    stretchGroup = av.getSelectionGroup();
-
-    if (stretchGroup == null)
-    {
-      stretchGroup = av.getAlignment().findGroup(sequence);
-      if (stretchGroup != null && res > stretchGroup.getStartRes()
-              && res < stretchGroup.getEndRes())
-      {
-        av.setSelectionGroup(stretchGroup);
-      }
-      else
-      {
-        stretchGroup = null;
-      }
-    }
-
-    else if (!stretchGroup.getSequences(null).contains(sequence)
-            || stretchGroup.getStartRes() > res
-            || stretchGroup.getEndRes() < res)
-    {
-      stretchGroup = null;
-
-      SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
-
-      if (allGroups != null)
-      {
-        for (int i = 0; i < allGroups.length; i++)
-        {
-          if (allGroups[i].getStartRes() <= res
-                  && allGroups[i].getEndRes() >= res)
-          {
-            stretchGroup = allGroups[i];
-            break;
-          }
-        }
-      }
-      av.setSelectionGroup(stretchGroup);
-    }
-
-    // DETECT RIGHT MOUSE BUTTON IN AWT
-    if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
-    {
-      SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
-              sequence.findPosition(res));
-
-      Vector<String> links = null;
-      if (allFeatures != null)
-      {
-        for (int i = 0; i < allFeatures.length; i++)
-        {
-          if (allFeatures[i].links != null)
-          {
-            if (links == null)
-            {
-              links = new Vector<String>();
-            }
-            for (int j = 0; j < allFeatures[i].links.size(); j++)
-            {
-              links.addElement(allFeatures[i].links.elementAt(j));
-            }
-          }
-        }
-      }
-      APopupMenu popup = new APopupMenu(ap, null, links);
-      this.add(popup);
-      popup.show(this, evt.getX(), evt.getY());
-      return;
-    }
-
-    if (av.cursorMode)
-    {
-      seqCanvas.cursorX = findRes(evt);
-      seqCanvas.cursorY = findSeq(evt);
-      seqCanvas.repaint();
-      return;
-    }
-
-    // Only if left mouse button do we want to change group sizes
-
-    if (stretchGroup == null)
-    {
-      // define a new group here
-      SequenceGroup sg = new SequenceGroup();
-      sg.setStartRes(res);
-      sg.setEndRes(res);
-      sg.addSequence(sequence, false);
-      av.setSelectionGroup(sg);
-      stretchGroup = sg;
-
-      if (av.getConservationSelected())
-      {
-        SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
-                "Background");
-      }
-      if (av.getAbovePIDThreshold())
-      {
-        SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
-                "Background");
-      }
-
-    }
-  }
-
-  public void doMouseReleasedDefineMode(MouseEvent evt)
-  {
-    if (stretchGroup == null)
-    {
-      return;
-    }
-
-    stretchGroup.recalcConservation(); // always do this - annotation has own
-                                       // state
-    if (stretchGroup.cs != null)
-    {
-      stretchGroup.cs.alignmentChanged(stretchGroup,
-              av.getHiddenRepSequences());
-
-      if (stretchGroup.cs.conservationApplied())
-      {
-        SliderPanel.setConservationSlider(ap, stretchGroup.cs,
-                stretchGroup.getName());
-      }
-      else
-      {
-        SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
-                stretchGroup.getName());
-      }
-    }
-    changeEndRes = false;
-    changeStartRes = false;
-    stretchGroup = null;
-    PaintRefresher.Refresh(ap, av.getSequenceSetId());
-    ap.paintAlignment(true);
-    av.sendSelection();
-  }
-
-  public void doMouseDraggedDefineMode(MouseEvent evt)
-  {
-    int res = findRes(evt);
-    int y = findSeq(evt);
-
-    if (wrappedBlock != startWrapBlock)
-    {
-      return;
-    }
-
-    if (stretchGroup == null)
-    {
-      return;
-    }
-
-    mouseDragging = true;
-
-    if (y > av.getAlignment().getHeight())
-    {
-      y = av.getAlignment().getHeight() - 1;
-    }
-
-    if (res >= av.getAlignment().getWidth())
-    {
-      res = av.getAlignment().getWidth() - 1;
-    }
-
-    if (stretchGroup.getEndRes() == res)
-    {
-      // Edit end res position of selected group
-      changeEndRes = true;
-    }
-    else if (stretchGroup.getStartRes() == res)
-    {
-      // Edit start res position of selected group
-      changeStartRes = true;
-    }
-
-    if (res < 0)
-    {
-      res = 0;
-    }
-
-    if (changeEndRes)
-    {
-      if (res > (stretchGroup.getStartRes() - 1))
-      {
-        stretchGroup.setEndRes(res);
-      }
-    }
-    else if (changeStartRes)
-    {
-      if (res < (stretchGroup.getEndRes() + 1))
-      {
-        stretchGroup.setStartRes(res);
-      }
-    }
-
-    int dragDirection = 0;
-
-    if (y > oldSeq)
-    {
-      dragDirection = 1;
-    }
-    else if (y < oldSeq)
-    {
-      dragDirection = -1;
-    }
-
-    while ((y != oldSeq) && (oldSeq > -1)
-            && (y < av.getAlignment().getHeight()))
-    {
-      // This routine ensures we don't skip any sequences, as the
-      // selection is quite slow.
-      Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
-
-      oldSeq += dragDirection;
-
-      if (oldSeq < 0)
-      {
-        break;
-      }
-
-      Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
-
-      if (stretchGroup.getSequences(null).contains(nextSeq))
-      {
-        stretchGroup.deleteSequence(seq, false);
-      }
-      else
-      {
-        if (seq != null)
-        {
-          stretchGroup.addSequence(seq, false);
-        }
-
-        stretchGroup.addSequence(nextSeq, false);
-      }
-    }
-
-    if (oldSeq < 0)
-    {
-      oldSeq = -1;
-    }
-
-    if (res > av.endRes || res < av.startRes || y < av.startSeq
-            || y > av.endSeq)
-    {
-      mouseExited(evt);
-    }
-
-    if (scrollThread != null)
-    {
-      scrollThread.setEvent(evt);
-    }
-
-    seqCanvas.repaint();
-  }
-
-  public void mouseEntered(MouseEvent e)
-  {
-    if (oldSeq < 0)
-    {
-      oldSeq = 0;
-    }
-
-    if (scrollThread != null)
-    {
-      scrollThread.running = false;
-      scrollThread = null;
-    }
-  }
-
-  public void mouseExited(MouseEvent e)
-  {
-    if (av.getWrapAlignment())
-    {
-      return;
-    }
-
-    if (mouseDragging && scrollThread == null)
-    {
-      scrollThread = new ScrollThread();
-    }
-  }
-
-  void scrollCanvas(MouseEvent evt)
-  {
-    if (evt == null)
-    {
-      if (scrollThread != null)
-      {
-        scrollThread.running = false;
-        scrollThread = null;
-      }
-      mouseDragging = false;
-    }
-    else
-    {
-      if (scrollThread == null)
-      {
-        scrollThread = new ScrollThread();
-      }
-
-      mouseDragging = true;
-      scrollThread.setEvent(evt);
-    }
-
-  }
-
-  // this class allows scrolling off the bottom of the visible alignment
-  class ScrollThread extends Thread
-  {
-    MouseEvent evt;
-
-    boolean running = false;
-
-    public ScrollThread()
-    {
-      start();
-    }
-
-    public void setEvent(MouseEvent e)
-    {
-      evt = e;
-    }
-
-    public void stopScrolling()
-    {
-      running = false;
-    }
-
-    public void run()
-    {
-      running = true;
-      while (running)
-      {
-
-        if (evt != null)
-        {
-
-          if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)
-          {
-            running = ap.scrollUp(true);
-          }
-
-          if (mouseDragging && evt.getY() >= getSize().height
-                  && av.getAlignment().getHeight() > av.getEndSeq())
-          {
-            running = ap.scrollUp(false);
-          }
-
-          if (mouseDragging && evt.getX() < 0)
-          {
-            running = ap.scrollRight(false);
-          }
-
-          else if (mouseDragging && evt.getX() >= getSize().width)
-          {
-            running = ap.scrollRight(true);
-          }
-        }
-
-        try
-        {
-          Thread.sleep(75);
-        } catch (Exception ex)
-        {
-        }
-      }
-    }
-  }
-
-  /**
-   * modify current selection according to a received message.
-   */
-  public void selection(SequenceGroup seqsel, ColumnSelection colsel,
-          SelectionSource source)
-  {
-    // TODO: fix this hack - source of messages is align viewport, but SeqPanel
-    // handles selection messages...
-    // TODO: extend config options to allow user to control if selections may be
-    // shared between viewports.
-    if (av != null
-            && (av == source || !av.followSelection || (source instanceof AlignViewport && ((AlignmentViewport) source)
-                    .getSequenceSetId().equals(av.getSequenceSetId()))))
-    {
-      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.getSelectionGroup() == null || !av.isSelectionGroupChanged(true))
-    {
-      SequenceGroup sgroup = null;
-      if (seqsel != null && seqsel.getSize() > 0)
-      {
-        if (av.getAlignment() == null)
-        {
-          System.out
-                  .println("Selection message: 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)
-      {
-        av.setSelectionGroup(sgroup);
-      }
-      else
-      {
-        av.setSelectionGroup(null);
-      }
-      repaint = av.isSelectionGroupChanged(true);
-    }
-    if (copycolsel
-            && (av.getColumnSelection() == null || !av
-                    .isColSelChanged(true)))
-    {
-      // the current selection is unset or from a previous message
-      // so import the new colsel.
-      if (colsel == null || colsel.size() == 0)
-      {
-        if (av.getColumnSelection() != null)
-        {
-          av.getColumnSelection().clear();
-        }
-      }
-      else
-      {
-        // TODO: shift colSel according to the intersecting sequences
-        if (av.getColumnSelection() == null)
-        {
-          av.setColumnSelection(new ColumnSelection(colsel));
-        }
-        else
-        {
-          av.getColumnSelection().setElementsFrom(colsel);
-        }
-      }
-      repaint |= av.isColSelChanged(true);
-    }
-    if (copycolsel
-            && av.hasHiddenColumns()
-            && (av.getColumnSelection() == null || av.getColumnSelection()
-                    .getHiddenColumns() == null))
-    {
-      System.err.println("Bad things");
-    }
-    if (repaint)
-    {
-      ap.scalePanelHolder.repaint();
-      ap.repaint();
-    }
-  }
-
-  /**
-   * scroll to the given row/column - or nearest visible location
-   * 
-   * @param row
-   * @param column
-   */
-  public void scrollTo(int row, int column)
-  {
-
-    row = row < 0 ? ap.av.startSeq : row;
-    column = column < 0 ? ap.av.startRes : column;
-    ap.scrollTo(column, column, row, true, true);
-  }
-
-  /**
-   * scroll to the given row - or nearest visible location
-   * 
-   * @param row
-   */
-  public void scrollToRow(int row)
-  {
-
-    row = row < 0 ? ap.av.startSeq : row;
-    ap.scrollTo(ap.av.startRes, ap.av.startRes, row, true, true);
-  }
-
-  /**
-   * scroll to the given column - or nearest visible location
-   * 
-   * @param column
-   */
-  public void scrollToColumn(int column)
-  {
-
-    column = column < 0 ? ap.av.startRes : column;
-    ap.scrollTo(column, column, ap.av.startSeq, true, true);
-  }
-
-  /**
-   * 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);
-  
-    ap.scalePanelHolder.repaint();
-    ap.repaint();
-  
-    return true;
-  }
-
-}
+/*\r
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)\r
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors\r
+ * \r
+ * This file is part of Jalview.\r
+ * \r
+ * Jalview is free software: you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License \r
+ * as published by the Free Software Foundation, either version 3\r
+ * of the License, or (at your option) any later version.\r
+ *  \r
+ * Jalview is distributed in the hope that it will be useful, but \r
+ * WITHOUT ANY WARRANTY; without even the implied warranty \r
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR \r
+ * PURPOSE.  See the GNU General Public License for more details.\r
+ * \r
+ * You should have received a copy of the GNU General Public License\r
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.\r
+ * The Jalview Authors are detailed in the 'AUTHORS' file.\r
+ */\r
+package jalview.appletgui;\r
+\r
+import jalview.api.AlignViewportI;\r
+import jalview.commands.EditCommand;\r
+import jalview.commands.EditCommand.Action;\r
+import jalview.datamodel.ColumnSelection;\r
+import jalview.datamodel.SearchResults;\r
+import jalview.datamodel.SearchResults.Match;\r
+import jalview.datamodel.Sequence;\r
+import jalview.datamodel.SequenceFeature;\r
+import jalview.datamodel.SequenceGroup;\r
+import jalview.datamodel.SequenceI;\r
+import jalview.schemes.ResidueProperties;\r
+import jalview.structure.SelectionListener;\r
+import jalview.structure.SelectionSource;\r
+import jalview.structure.SequenceListener;\r
+import jalview.structure.StructureSelectionManager;\r
+import jalview.structure.VamsasSource;\r
+import jalview.util.Comparison;\r
+import jalview.util.MappingUtils;\r
+import jalview.util.MessageManager;\r
+import jalview.viewmodel.AlignmentViewport;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Font;\r
+import java.awt.FontMetrics;\r
+import java.awt.Point;\r
+import java.awt.event.InputEvent;\r
+import java.awt.event.MouseEvent;\r
+import java.awt.event.MouseListener;\r
+import java.awt.event.MouseMotionListener;\r
+import java.util.List;\r
+import java.util.Vector;\r
+\r
+import javax.swing.JPanel;\r
+\r
+public class SeqPanel extends JPanel implements MouseMotionListener,\r
+        MouseListener, SequenceListener, SelectionListener\r
+{\r
+\r
+  public SeqCanvas seqCanvas;\r
+\r
+  public AlignmentPanel ap;\r
+\r
+  protected int lastres;\r
+\r
+  protected int startseq;\r
+\r
+  protected AlignViewport av;\r
+\r
+  // if character is inserted or deleted, we will need to recalculate the\r
+  // conservation\r
+  boolean seqEditOccurred = false;\r
+\r
+  ScrollThread scrollThread = null;\r
+\r
+  boolean mouseDragging = false;\r
+\r
+  boolean editingSeqs = false;\r
+\r
+  boolean groupEditing = false;\r
+\r
+  int oldSeq = -1;\r
+\r
+  boolean changeEndSeq = false;\r
+\r
+  boolean changeStartSeq = false;\r
+\r
+  boolean changeEndRes = false;\r
+\r
+  boolean changeStartRes = false;\r
+\r
+  SequenceGroup stretchGroup = null;\r
+\r
+  StringBuffer keyboardNo1;\r
+\r
+  StringBuffer keyboardNo2;\r
+\r
+  boolean mouseWheelPressed = false;\r
+\r
+  Point lastMousePress;\r
+\r
+  EditCommand editCommand;\r
+\r
+  StructureSelectionManager ssm;\r
+\r
+  public SeqPanel(AlignViewport avp, AlignmentPanel p)\r
+  {\r
+    this.av = avp;\r
+\r
+    seqCanvas = new SeqCanvas(avp);\r
+    setLayout(new BorderLayout());\r
+    add(seqCanvas);\r
+\r
+    ap = p;\r
+\r
+    seqCanvas.addMouseMotionListener(this);\r
+    seqCanvas.addMouseListener(this);\r
+    ssm = StructureSelectionManager.getStructureSelectionManager(av.applet);\r
+    ssm.addStructureViewerListener(this);\r
+    ssm.addSelectionListener(this);\r
+\r
+    seqCanvas.repaint();\r
+  }\r
+\r
+  void endEditing()\r
+  {\r
+    if (editCommand != null && editCommand.getSize() > 0)\r
+    {\r
+      ap.alignFrame.addHistoryItem(editCommand);\r
+      av.firePropertyChange("alignment", null, av.getAlignment()\r
+              .getSequences());\r
+    }\r
+\r
+    startseq = -1;\r
+    lastres = -1;\r
+    editingSeqs = false;\r
+    groupEditing = false;\r
+    keyboardNo1 = null;\r
+    keyboardNo2 = null;\r
+    editCommand = null;\r
+  }\r
+\r
+  void setCursorRow()\r
+  {\r
+    seqCanvas.cursorY = getKeyboardNo1() - 1;\r
+    scrollToVisible();\r
+  }\r
+\r
+  void setCursorColumn()\r
+  {\r
+    seqCanvas.cursorX = getKeyboardNo1() - 1;\r
+    scrollToVisible();\r
+  }\r
+\r
+  void setCursorRowAndColumn()\r
+  {\r
+    if (keyboardNo2 == null)\r
+    {\r
+      keyboardNo2 = new StringBuffer();\r
+    }\r
+    else\r
+    {\r
+      seqCanvas.cursorX = getKeyboardNo1() - 1;\r
+      seqCanvas.cursorY = getKeyboardNo2() - 1;\r
+      scrollToVisible();\r
+    }\r
+  }\r
+\r
+  void setCursorPosition()\r
+  {\r
+    SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);\r
+\r
+    seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;\r
+    scrollToVisible();\r
+  }\r
+\r
+  void moveCursor(int dx, int dy)\r
+  {\r
+    seqCanvas.cursorX += dx;\r
+    seqCanvas.cursorY += dy;\r
+    if (av.hasHiddenColumns()\r
+            && !av.getColumnSelection().isVisible(seqCanvas.cursorX))\r
+    {\r
+      int original = seqCanvas.cursorX - dx;\r
+      int maxWidth = av.getAlignment().getWidth();\r
+\r
+      while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)\r
+              && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)\r
+      {\r
+        seqCanvas.cursorX += dx;\r
+      }\r
+\r
+      if (seqCanvas.cursorX >= maxWidth\r
+              || !av.getColumnSelection().isVisible(seqCanvas.cursorX))\r
+      {\r
+        seqCanvas.cursorX = original;\r
+      }\r
+    }\r
+    scrollToVisible();\r
+  }\r
+\r
+  void scrollToVisible()\r
+  {\r
+    if (seqCanvas.cursorX < 0)\r
+    {\r
+      seqCanvas.cursorX = 0;\r
+    }\r
+    else if (seqCanvas.cursorX > av.getAlignment().getWidth() - 1)\r
+    {\r
+      seqCanvas.cursorX = av.getAlignment().getWidth() - 1;\r
+    }\r
+\r
+    if (seqCanvas.cursorY < 0)\r
+    {\r
+      seqCanvas.cursorY = 0;\r
+    }\r
+    else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)\r
+    {\r
+      seqCanvas.cursorY = av.getAlignment().getHeight() - 1;\r
+    }\r
+\r
+    endEditing();\r
+    if (av.getWrapAlignment())\r
+    {\r
+      ap.scrollToWrappedVisible(seqCanvas.cursorX);\r
+    }\r
+    else\r
+    {\r
+      while (seqCanvas.cursorY < av.startSeq)\r
+      {\r
+        ap.scrollUp(true);\r
+      }\r
+      while (seqCanvas.cursorY + 1 > av.endSeq)\r
+      {\r
+        ap.scrollUp(false);\r
+      }\r
+      while (seqCanvas.cursorX < av.getColumnSelection()\r
+              .adjustForHiddenColumns(av.startRes))\r
+      {\r
+\r
+        if (!ap.scrollRight(false))\r
+        {\r
+          break;\r
+        }\r
+      }\r
+      while (seqCanvas.cursorX > av.getColumnSelection()\r
+              .adjustForHiddenColumns(av.endRes))\r
+      {\r
+        if (!ap.scrollRight(true))\r
+        {\r
+          break;\r
+        }\r
+      }\r
+    }\r
+    setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),\r
+            seqCanvas.cursorX, seqCanvas.cursorY);\r
+\r
+    seqCanvas.repaint();\r
+  }\r
+\r
+  void setSelectionAreaAtCursor(boolean topLeft)\r
+  {\r
+    SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);\r
+\r
+    if (av.getSelectionGroup() != null)\r
+    {\r
+      SequenceGroup sg = av.getSelectionGroup();\r
+      // Find the top and bottom of this group\r
+      int min = av.getAlignment().getHeight(), max = 0;\r
+      for (int i = 0; i < sg.getSize(); i++)\r
+      {\r
+        int index = av.getAlignment().findIndex(sg.getSequenceAt(i));\r
+        if (index > max)\r
+        {\r
+          max = index;\r
+        }\r
+        if (index < min)\r
+        {\r
+          min = index;\r
+        }\r
+      }\r
+\r
+      max++;\r
+\r
+      if (topLeft)\r
+      {\r
+        sg.setStartRes(seqCanvas.cursorX);\r
+        if (sg.getEndRes() < seqCanvas.cursorX)\r
+        {\r
+          sg.setEndRes(seqCanvas.cursorX);\r
+        }\r
+\r
+        min = seqCanvas.cursorY;\r
+      }\r
+      else\r
+      {\r
+        sg.setEndRes(seqCanvas.cursorX);\r
+        if (sg.getStartRes() > seqCanvas.cursorX)\r
+        {\r
+          sg.setStartRes(seqCanvas.cursorX);\r
+        }\r
+\r
+        max = seqCanvas.cursorY + 1;\r
+      }\r
+\r
+      if (min > max)\r
+      {\r
+        // Only the user can do this\r
+        av.setSelectionGroup(null);\r
+      }\r
+      else\r
+      {\r
+        // Now add any sequences between min and max\r
+        sg.clear();\r
+        for (int i = min; i < max; i++)\r
+        {\r
+          sg.addSequence(av.getAlignment().getSequenceAt(i), false);\r
+        }\r
+      }\r
+    }\r
+\r
+    if (av.getSelectionGroup() == null)\r
+    {\r
+      SequenceGroup sg = new SequenceGroup();\r
+      sg.setStartRes(seqCanvas.cursorX);\r
+      sg.setEndRes(seqCanvas.cursorX);\r
+      sg.addSequence(sequence, false);\r
+      av.setSelectionGroup(sg);\r
+    }\r
+    ap.paintAlignment(false);\r
+    av.sendSelection();\r
+  }\r
+\r
+  void insertGapAtCursor(boolean group)\r
+  {\r
+    groupEditing = group;\r
+    startseq = seqCanvas.cursorY;\r
+    lastres = seqCanvas.cursorX;\r
+    editSequence(true, seqCanvas.cursorX + getKeyboardNo1());\r
+    endEditing();\r
+  }\r
+\r
+  void deleteGapAtCursor(boolean group)\r
+  {\r
+    groupEditing = group;\r
+    startseq = seqCanvas.cursorY;\r
+    lastres = seqCanvas.cursorX + getKeyboardNo1();\r
+    editSequence(false, seqCanvas.cursorX);\r
+    endEditing();\r
+  }\r
+\r
+  void numberPressed(char value)\r
+  {\r
+    if (keyboardNo1 == null)\r
+    {\r
+      keyboardNo1 = new StringBuffer();\r
+    }\r
+\r
+    if (keyboardNo2 != null)\r
+    {\r
+      keyboardNo2.append(value);\r
+    }\r
+    else\r
+    {\r
+      keyboardNo1.append(value);\r
+    }\r
+  }\r
+\r
+  int getKeyboardNo1()\r
+  {\r
+    try\r
+    {\r
+      if (keyboardNo1 != null)\r
+      {\r
+        int value = Integer.parseInt(keyboardNo1.toString());\r
+        keyboardNo1 = null;\r
+        return value;\r
+      }\r
+    } catch (Exception x)\r
+    {\r
+    }\r
+    keyboardNo1 = null;\r
+    return 1;\r
+  }\r
+\r
+  int getKeyboardNo2()\r
+  {\r
+    try\r
+    {\r
+      if (keyboardNo2 != null)\r
+      {\r
+        int value = Integer.parseInt(keyboardNo2.toString());\r
+        keyboardNo2 = null;\r
+        return value;\r
+      }\r
+    } catch (Exception x)\r
+    {\r
+    }\r
+    keyboardNo2 = null;\r
+    return 1;\r
+  }\r
+\r
+  /**\r
+   * Set status message in alignment panel\r
+   * \r
+   * @param sequence\r
+   *          aligned sequence object\r
+   * @param res\r
+   *          alignment column\r
+   * @param seq\r
+   *          index of sequence in alignment\r
+   * @return position of res in sequence\r
+   */\r
+  void setStatusMessage(SequenceI sequence, int res, int seq)\r
+  {\r
+    // TODO remove duplication of identical gui method\r
+    StringBuilder text = new StringBuilder(32);\r
+    String seqno = seq == -1 ? "" : " " + (seq + 1);\r
+    text.append("Sequence" + seqno + " ID: " + sequence.getName());\r
+\r
+    String residue = null;\r
+    /*\r
+     * Try to translate the display character to residue name (null for gap).\r
+     */\r
+    final String displayChar = String.valueOf(sequence.getCharAt(res));\r
+    if (av.getAlignment().isNucleotide())\r
+    {\r
+      residue = ResidueProperties.nucleotideName.get(displayChar);\r
+      if (residue != null)\r
+      {\r
+        text.append(" Nucleotide: ").append(residue);\r
+      }\r
+    }\r
+    else\r
+    {\r
+      residue = "X".equalsIgnoreCase(displayChar) ? "X"\r
+              : ResidueProperties.aa2Triplet.get(displayChar);\r
+      if (residue != null)\r
+      {\r
+        text.append(" Residue: ").append(residue);\r
+      }\r
+    }\r
+\r
+    int pos = -1;\r
+    if (residue != null)\r
+    {\r
+      pos = sequence.findPosition(res);\r
+      text.append(" (").append(Integer.toString(pos)).append(")");\r
+    }\r
+    // Object obj = null;\r
+    // if (av.getAlignment().isNucleotide())\r
+    // {\r
+    // obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)\r
+    // + "");\r
+    // if (obj != null)\r
+    // {\r
+    // text.append(" Nucleotide: ");\r
+    // }\r
+    // }\r
+    // else\r
+    // {\r
+    // obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");\r
+    // if (obj != null)\r
+    // {\r
+    // text.append("  Residue: ");\r
+    // }\r
+    // }\r
+    //\r
+    // if (obj != null)\r
+    // {\r
+    //\r
+    // if (obj != "")\r
+    // {\r
+    // text.append(obj + " (" + sequence.findPosition(res) + ")");\r
+    // }\r
+    // }\r
+\r
+    ap.alignFrame.statusBar.setText(text.toString());\r
+\r
+  }\r
+\r
+  /**\r
+   * Set the status bar message to highlight the first matched position in\r
+   * search results.\r
+   * \r
+   * @param results\r
+   */\r
+  private void setStatusMessage(SearchResults results)\r
+  {\r
+    List<Match> matches = results.getResults();\r
+    if (!matches.isEmpty())\r
+    {\r
+      Match m = matches.get(0);\r
+      SequenceI seq = m.getSequence();\r
+      int sequenceIndex = this.av.getAlignment().findIndex(seq);\r
+\r
+      /*\r
+       * Convert position in sequence (base 1) to sequence character array index\r
+       * (base 0)\r
+       */\r
+      int start = m.getStart() - 1;\r
+      setStatusMessage(seq, start, sequenceIndex);\r
+    }\r
+  }\r
+\r
+  public void mousePressed(MouseEvent evt)\r
+  {\r
+    lastMousePress = evt.getPoint();\r
+\r
+    // For now, ignore the mouseWheel font resizing on Macs\r
+    // As the Button2_mask always seems to be true\r
+    if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK\r
+            && !av.MAC)\r
+    {\r
+      mouseWheelPressed = true;\r
+      return;\r
+    }\r
+\r
+    if (evt.isShiftDown() || evt.isControlDown() || evt.isAltDown())\r
+    {\r
+      if (evt.isControlDown() || evt.isAltDown())\r
+      {\r
+        groupEditing = true;\r
+      }\r
+      editingSeqs = true;\r
+    }\r
+    else\r
+    {\r
+      doMousePressedDefineMode(evt);\r
+      return;\r
+    }\r
+\r
+    int seq = findSeq(evt);\r
+    int res = findRes(evt);\r
+\r
+    if (seq < 0 || res < 0)\r
+    {\r
+      return;\r
+    }\r
+\r
+    if ((seq < av.getAlignment().getHeight())\r
+            && (res < av.getAlignment().getSequenceAt(seq).getLength()))\r
+    {\r
+      startseq = seq;\r
+      lastres = res;\r
+    }\r
+    else\r
+    {\r
+      startseq = -1;\r
+      lastres = -1;\r
+    }\r
+\r
+    return;\r
+  }\r
+\r
+  public void mouseClicked(MouseEvent evt)\r
+  {\r
+    SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));\r
+    if (evt.getClickCount() > 1)\r
+    {\r
+      if (av.getSelectionGroup() != null\r
+              && av.getSelectionGroup().getSize() == 1\r
+              && av.getSelectionGroup().getEndRes()\r
+                      - av.getSelectionGroup().getStartRes() < 2)\r
+      {\r
+        av.setSelectionGroup(null);\r
+      }\r
+\r
+      SequenceFeature[] features = findFeaturesAtRes(sequence,\r
+              sequence.findPosition(findRes(evt)));\r
+\r
+      if (features != null && features.length > 0)\r
+      {\r
+        SearchResults highlight = new SearchResults();\r
+        highlight.addResult(sequence, features[0].getBegin(),\r
+                features[0].getEnd());\r
+        seqCanvas.highlightSearchResults(highlight);\r
+      }\r
+      if (features != null && features.length > 0)\r
+      {\r
+        seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]\r
+        { sequence }, features, false, ap);\r
+\r
+        seqCanvas.highlightSearchResults(null);\r
+      }\r
+    }\r
+  }\r
+\r
+  public void mouseReleased(MouseEvent evt)\r
+  {\r
+    mouseDragging = false;\r
+    mouseWheelPressed = false;\r
+    ap.paintAlignment(true);\r
+\r
+    if (!editingSeqs)\r
+    {\r
+      doMouseReleasedDefineMode(evt);\r
+      return;\r
+    }\r
+\r
+    endEditing();\r
+\r
+  }\r
+\r
+  int startWrapBlock = -1;\r
+\r
+  int wrappedBlock = -1;\r
+\r
+  int findRes(MouseEvent evt)\r
+  {\r
+    int res = 0;\r
+    int x = evt.getX();\r
+\r
+    if (av.getWrapAlignment())\r
+    {\r
+\r
+      int hgap = av.getCharHeight();\r
+      if (av.getScaleAboveWrapped())\r
+      {\r
+        hgap += av.getCharHeight();\r
+      }\r
+\r
+      int cHeight = av.getAlignment().getHeight() * av.getCharHeight()\r
+              + hgap\r
+              + seqCanvas.getAnnotationHeight();\r
+\r
+      int y = evt.getY();\r
+      y -= hgap;\r
+      x -= seqCanvas.LABEL_WEST;\r
+\r
+      int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);\r
+      if (cwidth < 1)\r
+      {\r
+        return 0;\r
+      }\r
+\r
+      wrappedBlock = y / cHeight;\r
+      wrappedBlock += av.getStartRes() / cwidth;\r
+\r
+      res = wrappedBlock * cwidth + x / av.getCharWidth();\r
+\r
+    }\r
+    else\r
+    {\r
+      res = (x / av.getCharWidth()) + av.getStartRes();\r
+    }\r
+\r
+    if (av.hasHiddenColumns())\r
+    {\r
+      res = av.getColumnSelection().adjustForHiddenColumns(res);\r
+    }\r
+\r
+    return res;\r
+\r
+  }\r
+\r
+  int findSeq(MouseEvent evt)\r
+  {\r
+    final int sqnum = findAlRow(evt);\r
+    return (sqnum < 0) ? 0 : sqnum;\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @param evt\r
+   * @return row in alignment that was selected (or -1 for column selection)\r
+   */\r
+  private int findAlRow(MouseEvent evt)\r
+  {\r
+    int seq = 0;\r
+    int y = evt.getY();\r
+\r
+    if (av.getWrapAlignment())\r
+    {\r
+      int hgap = av.getCharHeight();\r
+      if (av.getScaleAboveWrapped())\r
+      {\r
+        hgap += av.getCharHeight();\r
+      }\r
+\r
+      int cHeight = av.getAlignment().getHeight() * av.getCharHeight()\r
+              + hgap\r
+              + seqCanvas.getAnnotationHeight();\r
+\r
+      y -= hgap;\r
+\r
+      seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()\r
+              .getHeight() - 1);\r
+      if (seq < 0)\r
+      {\r
+        seq = -1;\r
+      }\r
+    }\r
+    else\r
+    {\r
+      seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av\r
+              .getAlignment().getHeight() - 1);\r
+      if (seq < 0)\r
+      {\r
+        seq = -1;\r
+      }\r
+    }\r
+\r
+    return seq;\r
+  }\r
+\r
+  public void doMousePressed(MouseEvent evt)\r
+  {\r
+\r
+    int seq = findSeq(evt);\r
+    int res = findRes(evt);\r
+\r
+    if (seq < av.getAlignment().getHeight()\r
+            && res < av.getAlignment().getSequenceAt(seq).getLength())\r
+    {\r
+      // char resstr = align.getSequenceAt(seq).getSequence().charAt(res);\r
+      // Find the residue's position in the sequence (res is the position\r
+      // in the alignment\r
+\r
+      startseq = seq;\r
+      lastres = res;\r
+    }\r
+    else\r
+    {\r
+      startseq = -1;\r
+      lastres = -1;\r
+    }\r
+\r
+    return;\r
+  }\r
+\r
+  String lastMessage;\r
+\r
+  public void mouseOverSequence(SequenceI sequence, int index, int pos)\r
+  {\r
+    String tmp = sequence.hashCode() + index + "";\r
+    if (lastMessage == null || !lastMessage.equals(tmp))\r
+    {\r
+      ssm.mouseOverSequence(sequence, index, pos, av);\r
+    }\r
+\r
+    lastMessage = tmp;\r
+  }\r
+\r
+  public void highlightSequence(SearchResults results)\r
+  {\r
+    if (av.isFollowHighlight())\r
+    {\r
+      if (ap.scrollToPosition(results, true))\r
+      {\r
+        ap.alignFrame.repaint();\r
+      }\r
+    }\r
+    setStatusMessage(results);\r
+    seqCanvas.highlightSearchResults(results);\r
+\r
+  }\r
+\r
+  @Override\r
+  public VamsasSource getVamsasSource()\r
+  {\r
+    return this.ap == null ? null : this.ap.av;\r
+  }\r
+\r
+  public void updateColours(SequenceI seq, int index)\r
+  {\r
+    System.out.println("update the seqPanel colours");\r
+    // repaint();\r
+  }\r
+\r
+  public void mouseMoved(MouseEvent evt)\r
+  {\r
+    int res = findRes(evt);\r
+    int seq = findSeq(evt);\r
+\r
+    if (seq >= av.getAlignment().getHeight() || seq < 0 || res < 0)\r
+    {\r
+      if (tooltip != null)\r
+      {\r
+        tooltip.setTip("");\r
+      }\r
+      return;\r
+    }\r
+\r
+    SequenceI sequence = av.getAlignment().getSequenceAt(seq);\r
+    if (res > sequence.getLength())\r
+    {\r
+      if (tooltip != null)\r
+      {\r
+        tooltip.setTip("");\r
+      }\r
+      return;\r
+    }\r
+\r
+    int respos = sequence.findPosition(res);\r
+    if (ssm != null)\r
+    {\r
+      mouseOverSequence(sequence, res, respos);\r
+    }\r
+\r
+    StringBuilder text = new StringBuilder();\r
+    text.append("Sequence ").append(Integer.toString(seq + 1))\r
+            .append(" ID: ").append(sequence.getName());\r
+\r
+    String obj = null;\r
+    final String ch = String.valueOf(sequence.getCharAt(res));\r
+    if (av.getAlignment().isNucleotide())\r
+    {\r
+      obj = ResidueProperties.nucleotideName.get(ch);\r
+      if (obj != null)\r
+      {\r
+        text.append(" Nucleotide: ").append(obj);\r
+      }\r
+    }\r
+    else\r
+    {\r
+      obj = "X".equalsIgnoreCase(ch) ? "X"\r
+              : ResidueProperties.aa2Triplet.get(ch);\r
+      if (obj != null)\r
+      {\r
+        text.append(" Residue: ").append(obj);\r
+      }\r
+    }\r
+\r
+    if (obj != null)\r
+    {\r
+      text.append(" (").append(Integer.toString(respos)).append(")");\r
+    }\r
+\r
+    ap.alignFrame.statusBar.setText(text.toString());\r
+\r
+    StringBuilder tooltipText = new StringBuilder();\r
+    SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);\r
+    if (groups != null)\r
+    {\r
+      for (int g = 0; g < groups.length; g++)\r
+      {\r
+        if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)\r
+        {\r
+          if (!groups[g].getName().startsWith("JTreeGroup")\r
+                  && !groups[g].getName().startsWith("JGroup"))\r
+          {\r
+            tooltipText.append(groups[g].getName()).append(" ");\r
+          }\r
+          if (groups[g].getDescription() != null)\r
+          {\r
+            tooltipText.append(groups[g].getDescription());\r
+          }\r
+          tooltipText.append("\n");\r
+        }\r
+      }\r
+    }\r
+\r
+    // use aa to see if the mouse pointer is on a\r
+    SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,\r
+            sequence.findPosition(res));\r
+\r
+    int index = 0;\r
+    while (index < allFeatures.length)\r
+    {\r
+      SequenceFeature sf = allFeatures[index];\r
+\r
+      tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);\r
+\r
+      if (sf.getDescription() != null)\r
+      {\r
+        tooltipText.append(" " + sf.getDescription());\r
+      }\r
+\r
+      if (sf.getValue("status") != null)\r
+      {\r
+        String status = sf.getValue("status").toString();\r
+        if (status.length() > 0)\r
+        {\r
+          tooltipText.append(" (" + sf.getValue("status") + ")");\r
+        }\r
+      }\r
+      tooltipText.append("\n");\r
+\r
+      index++;\r
+    }\r
+\r
+    if (tooltip == null)\r
+    {\r
+      tooltip = new Tooltip(tooltipText.toString(), seqCanvas);\r
+    }\r
+    else\r
+    {\r
+      tooltip.setTip(tooltipText.toString());\r
+    }\r
+  }\r
+\r
+  SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)\r
+  {\r
+    Vector tmp = new Vector();\r
+    SequenceFeature[] features = sequence.getSequenceFeatures();\r
+    if (features != null)\r
+    {\r
+      for (int i = 0; i < features.length; i++)\r
+      {\r
+        if (av.getFeaturesDisplayed() == null\r
+                || !av.getFeaturesDisplayed().isVisible(features[i].getType()))\r
+        {\r
+          continue;\r
+        }\r
+\r
+        if (features[i].featureGroup != null\r
+                && !seqCanvas.fr.checkGroupVisibility(features[i].featureGroup,false))\r
+        {\r
+          continue;\r
+        }\r
+\r
+        if ((features[i].getBegin() <= res)\r
+                && (features[i].getEnd() >= res))\r
+        {\r
+          tmp.addElement(features[i]);\r
+        }\r
+      }\r
+    }\r
+\r
+    features = new SequenceFeature[tmp.size()];\r
+    tmp.copyInto(features);\r
+\r
+    return features;\r
+  }\r
+\r
+  Tooltip tooltip;\r
+\r
+  public void mouseDragged(MouseEvent evt)\r
+  {\r
+    if (mouseWheelPressed)\r
+    {\r
+      int oldWidth = av.getCharWidth();\r
+\r
+      // Which is bigger, left-right or up-down?\r
+      if (Math.abs(evt.getY() - lastMousePress.y) > Math.abs(evt.getX()\r
+              - lastMousePress.x))\r
+      {\r
+        int fontSize = av.font.getSize();\r
+\r
+        if (evt.getY() < lastMousePress.y && av.getCharHeight() > 1)\r
+        {\r
+          fontSize--;\r
+        }\r
+        else if (evt.getY() > lastMousePress.y)\r
+        {\r
+          fontSize++;\r
+        }\r
+\r
+        if (fontSize < 1)\r
+        {\r
+          fontSize = 1;\r
+        }\r
+\r
+        av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));\r
+        av.setCharWidth(oldWidth);\r
+      }\r
+      else\r
+      {\r
+        if (evt.getX() < lastMousePress.x && av.getCharWidth() > 1)\r
+        {\r
+          av.setCharWidth(av.getCharWidth() - 1);\r
+        }\r
+        else if (evt.getX() > lastMousePress.x)\r
+        {\r
+          av.setCharWidth(av.getCharWidth() + 1);\r
+        }\r
+\r
+        if (av.getCharWidth() < 1)\r
+        {\r
+          av.setCharWidth(1);\r
+        }\r
+      }\r
+\r
+      ap.fontChanged();\r
+\r
+      FontMetrics fm = getFontMetrics(av.getFont());\r
+      av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();\r
+\r
+      lastMousePress = evt.getPoint();\r
+\r
+      ap.paintAlignment(false);\r
+      ap.annotationPanel.image = null;\r
+      return;\r
+    }\r
+\r
+    if (!editingSeqs)\r
+    {\r
+      doMouseDraggedDefineMode(evt);\r
+      return;\r
+    }\r
+\r
+    int res = findRes(evt);\r
+\r
+    if (res < 0)\r
+    {\r
+      res = 0;\r
+    }\r
+\r
+    if ((lastres == -1) || (lastres == res))\r
+    {\r
+      return;\r
+    }\r
+\r
+    if ((res < av.getAlignment().getWidth()) && (res < lastres))\r
+    {\r
+      // dragLeft, delete gap\r
+      editSequence(false, res);\r
+    }\r
+    else\r
+    {\r
+      editSequence(true, res);\r
+    }\r
+\r
+    mouseDragging = true;\r
+    if (scrollThread != null)\r
+    {\r
+      scrollThread.setEvent(evt);\r
+    }\r
+\r
+  }\r
+\r
+  synchronized void editSequence(boolean insertGap, int startres)\r
+  {\r
+    int fixedLeft = -1;\r
+    int fixedRight = -1;\r
+    boolean fixedColumns = false;\r
+    SequenceGroup sg = av.getSelectionGroup();\r
+\r
+    SequenceI seq = av.getAlignment().getSequenceAt(startseq);\r
+\r
+    if (!groupEditing && av.hasHiddenRows())\r
+    {\r
+      if (av.isHiddenRepSequence(seq))\r
+      {\r
+        sg = av.getRepresentedSequences(seq);\r
+        groupEditing = true;\r
+      }\r
+    }\r
+\r
+    StringBuffer message = new StringBuffer();\r
+    if (groupEditing)\r
+    {\r
+      message.append(MessageManager.getString("action.edit_group")).append(\r
+              ":");\r
+      if (editCommand == null)\r
+      {\r
+        editCommand = new EditCommand(\r
+                MessageManager.getString("action.edit_group"));\r
+      }\r
+    }\r
+    else\r
+    {\r
+      message.append(MessageManager.getString("label.edit_sequence"))\r
+              .append(" " + seq.getName());\r
+      String label = seq.getName();\r
+      if (label.length() > 10)\r
+      {\r
+        label = label.substring(0, 10);\r
+      }\r
+      if (editCommand == null)\r
+      {\r
+        editCommand = new EditCommand(MessageManager.formatMessage(\r
+                "label.edit_params", new String[]\r
+                { label }));\r
+      }\r
+    }\r
+\r
+    if (insertGap)\r
+    {\r
+      message.append(" insert ");\r
+    }\r
+    else\r
+    {\r
+      message.append(" delete ");\r
+    }\r
+\r
+    message.append(Math.abs(startres - lastres) + " gaps.");\r
+    ap.alignFrame.statusBar.setText(message.toString());\r
+\r
+    // Are we editing within a selection group?\r
+    if (groupEditing\r
+            || (sg != null && sg.getSequences(av.getHiddenRepSequences())\r
+                    .contains(seq)))\r
+    {\r
+      fixedColumns = true;\r
+\r
+      // sg might be null as the user may only see 1 sequence,\r
+      // but the sequence represents a group\r
+      if (sg == null)\r
+      {\r
+        if (!av.isHiddenRepSequence(seq))\r
+        {\r
+          endEditing();\r
+          return;\r
+        }\r
+\r
+        sg = av.getRepresentedSequences(seq);\r
+      }\r
+\r
+      fixedLeft = sg.getStartRes();\r
+      fixedRight = sg.getEndRes();\r
+\r
+      if ((startres < fixedLeft && lastres >= fixedLeft)\r
+              || (startres >= fixedLeft && lastres < fixedLeft)\r
+              || (startres > fixedRight && lastres <= fixedRight)\r
+              || (startres <= fixedRight && lastres > fixedRight))\r
+      {\r
+        endEditing();\r
+        return;\r
+      }\r
+\r
+      if (fixedLeft > startres)\r
+      {\r
+        fixedRight = fixedLeft - 1;\r
+        fixedLeft = 0;\r
+      }\r
+      else if (fixedRight < startres)\r
+      {\r
+        fixedLeft = fixedRight;\r
+        fixedRight = -1;\r
+      }\r
+    }\r
+\r
+    if (av.hasHiddenColumns())\r
+    {\r
+      fixedColumns = true;\r
+      int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);\r
+      int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);\r
+\r
+      if ((insertGap && startres > y1 && lastres < y1)\r
+              || (!insertGap && startres < y2 && lastres > y2))\r
+      {\r
+        endEditing();\r
+        return;\r
+      }\r
+\r
+      // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");\r
+      // Selection spans a hidden region\r
+      if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))\r
+      {\r
+        if (startres >= y2)\r
+        {\r
+          fixedLeft = y2;\r
+        }\r
+        else\r
+        {\r
+          fixedRight = y2 - 1;\r
+        }\r
+      }\r
+    }\r
+\r
+    if (groupEditing)\r
+    {\r
+      SequenceI[] groupSeqs = sg.getSequences(av.getHiddenRepSequences())\r
+              .toArray(new SequenceI[0]);\r
+\r
+      // drag to right\r
+      if (insertGap)\r
+      {\r
+        // If the user has selected the whole sequence, and is dragging to\r
+        // the right, we can still extend the alignment and selectionGroup\r
+        if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight\r
+                && sg.getEndRes() == av.getAlignment().getWidth() - 1)\r
+        {\r
+          sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);\r
+          fixedRight = sg.getEndRes();\r
+        }\r
+\r
+        // Is it valid with fixed columns??\r
+        // Find the next gap before the end\r
+        // of the visible region boundary\r
+        boolean blank = false;\r
+        for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)\r
+        {\r
+          blank = true;\r
+\r
+          for (SequenceI gs : groupSeqs)\r
+          {\r
+            for (int j = 0; j < startres - lastres; j++)\r
+            {\r
+              if (!Comparison.isGap(gs.getCharAt(fixedRight\r
+                      - j)))\r
+              {\r
+                blank = false;\r
+                break;\r
+              }\r
+            }\r
+          }\r
+          if (blank)\r
+          {\r
+            break;\r
+          }\r
+        }\r
+\r
+        if (!blank)\r
+        {\r
+          if (sg.getSize() == av.getAlignment().getHeight())\r
+          {\r
+            if ((av.hasHiddenColumns() && startres < av\r
+                    .getColumnSelection().getHiddenBoundaryRight(startres)))\r
+            {\r
+              endEditing();\r
+              return;\r
+            }\r
+\r
+            int alWidth = av.getAlignment().getWidth();\r
+            if (av.hasHiddenRows())\r
+            {\r
+              int hwidth = av.getAlignment().getHiddenSequences()\r
+                      .getWidth();\r
+              if (hwidth > alWidth)\r
+              {\r
+                alWidth = hwidth;\r
+              }\r
+            }\r
+            // We can still insert gaps if the selectionGroup\r
+            // contains all the sequences\r
+            sg.setEndRes(sg.getEndRes() + startres - lastres);\r
+            fixedRight = alWidth + startres - lastres;\r
+          }\r
+          else\r
+          {\r
+            endEditing();\r
+            return;\r
+          }\r
+        }\r
+      }\r
+\r
+      // drag to left\r
+      else if (!insertGap)\r
+      {\r
+        // / Are we able to delete?\r
+        // ie are all columns blank?\r
+\r
+        for (SequenceI gs : groupSeqs)\r
+        {\r
+          for (int j = startres; j < lastres; j++)\r
+          {\r
+            if (gs.getLength() <= j)\r
+            {\r
+              continue;\r
+            }\r
+\r
+            if (!Comparison.isGap(gs.getCharAt(j)))\r
+            {\r
+              // Not a gap, block edit not valid\r
+              endEditing();\r
+              return;\r
+            }\r
+          }\r
+        }\r
+      }\r
+\r
+      if (insertGap)\r
+      {\r
+        // dragging to the right\r
+        if (fixedColumns && fixedRight != -1)\r
+        {\r
+          for (int j = lastres; j < startres; j++)\r
+          {\r
+            insertChar(j, groupSeqs, fixedRight);\r
+          }\r
+        }\r
+        else\r
+        {\r
+          editCommand.appendEdit(Action.INSERT_GAP, groupSeqs, startres,\r
+                  startres - lastres, av.getAlignment(), true);\r
+        }\r
+      }\r
+      else\r
+      {\r
+        // dragging to the left\r
+        if (fixedColumns && fixedRight != -1)\r
+        {\r
+          for (int j = lastres; j > startres; j--)\r
+          {\r
+            deleteChar(startres, groupSeqs, fixedRight);\r
+          }\r
+        }\r
+        else\r
+        {\r
+          editCommand.appendEdit(Action.DELETE_GAP, groupSeqs, startres,\r
+                  lastres - startres, av.getAlignment(), true);\r
+        }\r
+\r
+      }\r
+    }\r
+    else\r
+    // ///Editing a single sequence///////////\r
+    {\r
+      if (insertGap)\r
+      {\r
+        // dragging to the right\r
+        if (fixedColumns && fixedRight != -1)\r
+        {\r
+          for (int j = lastres; j < startres; j++)\r
+          {\r
+            insertChar(j, new SequenceI[]\r
+            { seq }, fixedRight);\r
+          }\r
+        }\r
+        else\r
+        {\r
+          editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[]\r
+          { seq }, lastres, startres - lastres, av.getAlignment(), true);\r
+        }\r
+      }\r
+      else\r
+      {\r
+        // dragging to the left\r
+        if (fixedColumns && fixedRight != -1)\r
+        {\r
+          for (int j = lastres; j > startres; j--)\r
+          {\r
+            if (!Comparison.isGap(seq.getCharAt(startres)))\r
+            {\r
+              endEditing();\r
+              break;\r
+            }\r
+            deleteChar(startres, new SequenceI[]\r
+            { seq }, fixedRight);\r
+          }\r
+        }\r
+        else\r
+        {\r
+          // could be a keyboard edit trying to delete none gaps\r
+          int max = 0;\r
+          for (int m = startres; m < lastres; m++)\r
+          {\r
+            if (!Comparison.isGap(seq.getCharAt(m)))\r
+            {\r
+              break;\r
+            }\r
+            max++;\r
+          }\r
+\r
+          if (max > 0)\r
+          {\r
+            editCommand.appendEdit(Action.DELETE_GAP, new SequenceI[]\r
+            { seq }, startres, max, av.getAlignment(), true);\r
+          }\r
+        }\r
+      }\r
+    }\r
+\r
+    lastres = startres;\r
+    seqCanvas.repaint();\r
+  }\r
+\r
+  void insertChar(int j, SequenceI[] seq, int fixedColumn)\r
+  {\r
+    int blankColumn = fixedColumn;\r
+    for (int s = 0; s < seq.length; s++)\r
+    {\r
+      // Find the next gap before the end of the visible region boundary\r
+      // If lastCol > j, theres a boundary after the gap insertion\r
+\r
+      for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)\r
+      {\r
+        if (Comparison.isGap(seq[s].getCharAt(blankColumn)))\r
+        {\r
+          // Theres a space, so break and insert the gap\r
+          break;\r
+        }\r
+      }\r
+\r
+      if (blankColumn <= j)\r
+      {\r
+        blankColumn = fixedColumn;\r
+        endEditing();\r
+        return;\r
+      }\r
+    }\r
+\r
+    editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,\r
+            av.getAlignment(), true);\r
+\r
+    editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1, av.getAlignment(),\r
+            true);\r
+\r
+  }\r
+\r
+  void deleteChar(int j, SequenceI[] seq, int fixedColumn)\r
+  {\r
+\r
+    editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1, av.getAlignment(),\r
+            true);\r
+\r
+    editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,\r
+            av.getAlignment(), true);\r
+  }\r
+\r
+  // ////////////////////////////////////////\r
+  // ///Everything below this is for defining the boundary of the rubberband\r
+  // ////////////////////////////////////////\r
+  public void doMousePressedDefineMode(MouseEvent evt)\r
+  {\r
+    if (scrollThread != null)\r
+    {\r
+      scrollThread.running = false;\r
+      scrollThread = null;\r
+    }\r
+\r
+    int res = findRes(evt);\r
+    int seq = findSeq(evt);\r
+    oldSeq = seq;\r
+    startWrapBlock = wrappedBlock;\r
+\r
+    if (seq == -1)\r
+    {\r
+      return;\r
+    }\r
+\r
+    SequenceI sequence = av.getAlignment().getSequenceAt(seq);\r
+\r
+    if (sequence == null || res > sequence.getLength())\r
+    {\r
+      return;\r
+    }\r
+\r
+    stretchGroup = av.getSelectionGroup();\r
+\r
+    if (stretchGroup == null)\r
+    {\r
+      stretchGroup = av.getAlignment().findGroup(sequence);\r
+      if (stretchGroup != null && res > stretchGroup.getStartRes()\r
+              && res < stretchGroup.getEndRes())\r
+      {\r
+        av.setSelectionGroup(stretchGroup);\r
+      }\r
+      else\r
+      {\r
+        stretchGroup = null;\r
+      }\r
+    }\r
+\r
+    else if (!stretchGroup.getSequences(null).contains(sequence)\r
+            || stretchGroup.getStartRes() > res\r
+            || stretchGroup.getEndRes() < res)\r
+    {\r
+      stretchGroup = null;\r
+\r
+      SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);\r
+\r
+      if (allGroups != null)\r
+      {\r
+        for (int i = 0; i < allGroups.length; i++)\r
+        {\r
+          if (allGroups[i].getStartRes() <= res\r
+                  && allGroups[i].getEndRes() >= res)\r
+          {\r
+            stretchGroup = allGroups[i];\r
+            break;\r
+          }\r
+        }\r
+      }\r
+      av.setSelectionGroup(stretchGroup);\r
+    }\r
+\r
+    // DETECT RIGHT MOUSE BUTTON IN AWT\r
+    if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)\r
+    {\r
+      SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,\r
+              sequence.findPosition(res));\r
+\r
+      Vector<String> links = null;\r
+      if (allFeatures != null)\r
+      {\r
+        for (int i = 0; i < allFeatures.length; i++)\r
+        {\r
+          if (allFeatures[i].links != null)\r
+          {\r
+            if (links == null)\r
+            {\r
+              links = new Vector<String>();\r
+            }\r
+            for (int j = 0; j < allFeatures[i].links.size(); j++)\r
+            {\r
+              links.addElement(allFeatures[i].links.elementAt(j));\r
+            }\r
+          }\r
+        }\r
+      }\r
+      APopupMenu popup = new APopupMenu(ap, null, links);\r
+      this.add(popup);\r
+      popup.show(this, evt.getX(), evt.getY());\r
+      return;\r
+    }\r
+\r
+    if (av.cursorMode)\r
+    {\r
+      seqCanvas.cursorX = findRes(evt);\r
+      seqCanvas.cursorY = findSeq(evt);\r
+      seqCanvas.repaint();\r
+      return;\r
+    }\r
+\r
+    // Only if left mouse button do we want to change group sizes\r
+\r
+    if (stretchGroup == null)\r
+    {\r
+      // define a new group here\r
+      SequenceGroup sg = new SequenceGroup();\r
+      sg.setStartRes(res);\r
+      sg.setEndRes(res);\r
+      sg.addSequence(sequence, false);\r
+      av.setSelectionGroup(sg);\r
+      stretchGroup = sg;\r
+\r
+      if (av.getConservationSelected())\r
+      {\r
+        SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),\r
+                "Background");\r
+      }\r
+      if (av.getAbovePIDThreshold())\r
+      {\r
+        SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),\r
+                "Background");\r
+      }\r
+\r
+    }\r
+  }\r
+\r
+  public void doMouseReleasedDefineMode(MouseEvent evt)\r
+  {\r
+    if (stretchGroup == null)\r
+    {\r
+      return;\r
+    }\r
+\r
+    stretchGroup.recalcConservation(); // always do this - annotation has own\r
+                                       // state\r
+    if (stretchGroup.cs != null)\r
+    {\r
+      stretchGroup.cs.alignmentChanged(stretchGroup,\r
+              av.getHiddenRepSequences());\r
+\r
+      if (stretchGroup.cs.conservationApplied())\r
+      {\r
+        SliderPanel.setConservationSlider(ap, stretchGroup.cs,\r
+                stretchGroup.getName());\r
+      }\r
+      else\r
+      {\r
+        SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,\r
+                stretchGroup.getName());\r
+      }\r
+    }\r
+    changeEndRes = false;\r
+    changeStartRes = false;\r
+    stretchGroup = null;\r
+    PaintRefresher.Refresh(ap, av.getSequenceSetId());\r
+    ap.paintAlignment(true);\r
+    av.sendSelection();\r
+  }\r
+\r
+  public void doMouseDraggedDefineMode(MouseEvent evt)\r
+  {\r
+    int res = findRes(evt);\r
+    int y = findSeq(evt);\r
+\r
+    if (wrappedBlock != startWrapBlock)\r
+    {\r
+      return;\r
+    }\r
+\r
+    if (stretchGroup == null)\r
+    {\r
+      return;\r
+    }\r
+\r
+    mouseDragging = true;\r
+\r
+    if (y > av.getAlignment().getHeight())\r
+    {\r
+      y = av.getAlignment().getHeight() - 1;\r
+    }\r
+\r
+    if (res >= av.getAlignment().getWidth())\r
+    {\r
+      res = av.getAlignment().getWidth() - 1;\r
+    }\r
+\r
+    if (stretchGroup.getEndRes() == res)\r
+    {\r
+      // Edit end res position of selected group\r
+      changeEndRes = true;\r
+    }\r
+    else if (stretchGroup.getStartRes() == res)\r
+    {\r
+      // Edit start res position of selected group\r
+      changeStartRes = true;\r
+    }\r
+\r
+    if (res < 0)\r
+    {\r
+      res = 0;\r
+    }\r
+\r
+    if (changeEndRes)\r
+    {\r
+      if (res > (stretchGroup.getStartRes() - 1))\r
+      {\r
+        stretchGroup.setEndRes(res);\r
+      }\r
+    }\r
+    else if (changeStartRes)\r
+    {\r
+      if (res < (stretchGroup.getEndRes() + 1))\r
+      {\r
+        stretchGroup.setStartRes(res);\r
+      }\r
+    }\r
+\r
+    int dragDirection = 0;\r
+\r
+    if (y > oldSeq)\r
+    {\r
+      dragDirection = 1;\r
+    }\r
+    else if (y < oldSeq)\r
+    {\r
+      dragDirection = -1;\r
+    }\r
+\r
+    while ((y != oldSeq) && (oldSeq > -1)\r
+            && (y < av.getAlignment().getHeight()))\r
+    {\r
+      // This routine ensures we don't skip any sequences, as the\r
+      // selection is quite slow.\r
+      Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
+\r
+      oldSeq += dragDirection;\r
+\r
+      if (oldSeq < 0)\r
+      {\r
+        break;\r
+      }\r
+\r
+      Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
+\r
+      if (stretchGroup.getSequences(null).contains(nextSeq))\r
+      {\r
+        stretchGroup.deleteSequence(seq, false);\r
+      }\r
+      else\r
+      {\r
+        if (seq != null)\r
+        {\r
+          stretchGroup.addSequence(seq, false);\r
+        }\r
+\r
+        stretchGroup.addSequence(nextSeq, false);\r
+      }\r
+    }\r
+\r
+    if (oldSeq < 0)\r
+    {\r
+      oldSeq = -1;\r
+    }\r
+\r
+    if (res > av.endRes || res < av.startRes || y < av.startSeq\r
+            || y > av.endSeq)\r
+    {\r
+      mouseExited(evt);\r
+    }\r
+\r
+    if (scrollThread != null)\r
+    {\r
+      scrollThread.setEvent(evt);\r
+    }\r
+\r
+    seqCanvas.repaint();\r
+  }\r
+\r
+  public void mouseEntered(MouseEvent e)\r
+  {\r
+    if (oldSeq < 0)\r
+    {\r
+      oldSeq = 0;\r
+    }\r
+\r
+    if (scrollThread != null)\r
+    {\r
+      scrollThread.running = false;\r
+      scrollThread = null;\r
+    }\r
+  }\r
+\r
+  public void mouseExited(MouseEvent e)\r
+  {\r
+    if (av.getWrapAlignment())\r
+    {\r
+      return;\r
+    }\r
+\r
+    if (mouseDragging && scrollThread == null)\r
+    {\r
+      scrollThread = new ScrollThread();\r
+    }\r
+  }\r
+\r
+  void scrollCanvas(MouseEvent evt)\r
+  {\r
+    if (evt == null)\r
+    {\r
+      if (scrollThread != null)\r
+      {\r
+        scrollThread.running = false;\r
+        scrollThread = null;\r
+      }\r
+      mouseDragging = false;\r
+    }\r
+    else\r
+    {\r
+      if (scrollThread == null)\r
+      {\r
+        scrollThread = new ScrollThread();\r
+      }\r
+\r
+      mouseDragging = true;\r
+      scrollThread.setEvent(evt);\r
+    }\r
+\r
+  }\r
+\r
+  // this class allows scrolling off the bottom of the visible alignment\r
+  class ScrollThread extends Thread\r
+  {\r
+    MouseEvent evt;\r
+\r
+    boolean running = false;\r
+\r
+    public ScrollThread()\r
+    {\r
+      start();\r
+    }\r
+\r
+    public void setEvent(MouseEvent e)\r
+    {\r
+      evt = e;\r
+    }\r
+\r
+    public void stopScrolling()\r
+    {\r
+      running = false;\r
+    }\r
+\r
+    public void run()\r
+    {\r
+      running = true;\r
+      while (running)\r
+      {\r
+\r
+        if (evt != null)\r
+        {\r
+\r
+          if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)\r
+          {\r
+            running = ap.scrollUp(true);\r
+          }\r
+\r
+          if (mouseDragging && evt.getY() >= getSize().height\r
+                  && av.getAlignment().getHeight() > av.getEndSeq())\r
+          {\r
+            running = ap.scrollUp(false);\r
+          }\r
+\r
+          if (mouseDragging && evt.getX() < 0)\r
+          {\r
+            running = ap.scrollRight(false);\r
+          }\r
+\r
+          else if (mouseDragging && evt.getX() >= getSize().width)\r
+          {\r
+            running = ap.scrollRight(true);\r
+          }\r
+        }\r
+\r
+        try\r
+        {\r
+          Thread.sleep(75);\r
+        } catch (Exception ex)\r
+        {\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * modify current selection according to a received message.\r
+   */\r
+  public void selection(SequenceGroup seqsel, ColumnSelection colsel,\r
+          SelectionSource source)\r
+  {\r
+    // TODO: fix this hack - source of messages is align viewport, but SeqPanel\r
+    // handles selection messages...\r
+    // TODO: extend config options to allow user to control if selections may be\r
+    // shared between viewports.\r
+    if (av != null\r
+            && (av == source || !av.followSelection || (source instanceof AlignViewport && ((AlignmentViewport) source)\r
+                    .getSequenceSetId().equals(av.getSequenceSetId()))))\r
+    {\r
+      return;\r
+    }\r
+\r
+    /*\r
+     * Check for selection in a view of which this one is a dna/protein\r
+     * complement.\r
+     */\r
+    if (selectionFromTranslation(seqsel, colsel, source))\r
+    {\r
+      return;\r
+    }\r
+\r
+    // do we want to thread this ? (contention with seqsel and colsel locks, I\r
+    // suspect)\r
+    // rules are: colsel is copied if there is a real intersection between\r
+    // sequence selection\r
+    boolean repaint = false, copycolsel = true;\r
+    if (av.getSelectionGroup() == null || !av.isSelectionGroupChanged(true))\r
+    {\r
+      SequenceGroup sgroup = null;\r
+      if (seqsel != null && seqsel.getSize() > 0)\r
+      {\r
+        if (av.getAlignment() == null)\r
+        {\r
+          System.out\r
+                  .println("Selection message: alignviewport av SeqSetId="\r
+                          + av.getSequenceSetId() + " ViewId="\r
+                          + av.getViewId()\r
+                          + " 's alignment is NULL! returning immediatly.");\r
+          return;\r
+        }\r
+        sgroup = seqsel.intersect(av.getAlignment(),\r
+                (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);\r
+        if ((sgroup == null || sgroup.getSize() == 0)\r
+                && (colsel == null || colsel.size() == 0))\r
+        {\r
+          // don't copy columns if the region didn't intersect.\r
+          copycolsel = false;\r
+        }\r
+      }\r
+      if (sgroup != null && sgroup.getSize() > 0)\r
+      {\r
+        av.setSelectionGroup(sgroup);\r
+      }\r
+      else\r
+      {\r
+        av.setSelectionGroup(null);\r
+      }\r
+      repaint = av.isSelectionGroupChanged(true);\r
+    }\r
+    if (copycolsel\r
+            && (av.getColumnSelection() == null || !av\r
+                    .isColSelChanged(true)))\r
+    {\r
+      // the current selection is unset or from a previous message\r
+      // so import the new colsel.\r
+      if (colsel == null || colsel.size() == 0)\r
+      {\r
+        if (av.getColumnSelection() != null)\r
+        {\r
+          av.getColumnSelection().clear();\r
+        }\r
+      }\r
+      else\r
+      {\r
+        // TODO: shift colSel according to the intersecting sequences\r
+        if (av.getColumnSelection() == null)\r
+        {\r
+          av.setColumnSelection(new ColumnSelection(colsel));\r
+        }\r
+        else\r
+        {\r
+          av.getColumnSelection().setElementsFrom(colsel);\r
+        }\r
+      }\r
+      repaint |= av.isColSelChanged(true);\r
+    }\r
+    if (copycolsel\r
+            && av.hasHiddenColumns()\r
+            && (av.getColumnSelection() == null || av.getColumnSelection()\r
+                    .getHiddenColumns() == null))\r
+    {\r
+      System.err.println("Bad things");\r
+    }\r
+    if (repaint)\r
+    {\r
+      ap.scalePanelHolder.repaint();\r
+      ap.repaint();\r
+    }\r
+  }\r
+\r
+  /**\r
+   * scroll to the given row/column - or nearest visible location\r
+   * \r
+   * @param row\r
+   * @param column\r
+   */\r
+  public void scrollTo(int row, int column)\r
+  {\r
+\r
+    row = row < 0 ? ap.av.startSeq : row;\r
+    column = column < 0 ? ap.av.startRes : column;\r
+    ap.scrollTo(column, column, row, true, true);\r
+  }\r
+\r
+  /**\r
+   * scroll to the given row - or nearest visible location\r
+   * \r
+   * @param row\r
+   */\r
+  public void scrollToRow(int row)\r
+  {\r
+\r
+    row = row < 0 ? ap.av.startSeq : row;\r
+    ap.scrollTo(ap.av.startRes, ap.av.startRes, row, true, true);\r
+  }\r
+\r
+  /**\r
+   * scroll to the given column - or nearest visible location\r
+   * \r
+   * @param column\r
+   */\r
+  public void scrollToColumn(int column)\r
+  {\r
+\r
+    column = column < 0 ? ap.av.startRes : column;\r
+    ap.scrollTo(column, column, ap.av.startSeq, true, true);\r
+  }\r
+\r
+  /**\r
+   * If this panel is a cdna/protein translation view of the selection source,\r
+   * tries to map the source selection to a local one, and returns true. Else\r
+   * returns false.\r
+   * \r
+   * @param seqsel\r
+   * @param colsel\r
+   * @param source\r
+   */\r
+  protected boolean selectionFromTranslation(SequenceGroup seqsel,\r
+          ColumnSelection colsel, SelectionSource source)\r
+  {\r
+    if (!(source instanceof AlignViewportI)) {\r
+      return false;\r
+    }\r
+    final AlignViewportI sourceAv = (AlignViewportI) source;\r
+    if (sourceAv.getCodingComplement() != av && av.getCodingComplement() != sourceAv)\r
+    {\r
+      return false;\r
+    }\r
+  \r
+    /*\r
+     * Map sequence selection\r
+     */\r
+    SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);\r
+    av.setSelectionGroup(sg);\r
+    av.isSelectionGroupChanged(true);\r
+  \r
+    /*\r
+     * Map column selection\r
+     */\r
+    ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,\r
+            av);\r
+    av.setColumnSelection(cs);\r
+    av.isColSelChanged(true);\r
+  \r
+    ap.scalePanelHolder.repaint();\r
+    ap.repaint();\r
+  \r
+    return true;\r
+  }\r
+\r
+}\r