Merge branch 'develop' into features/JAL-2446NCList
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 28 Jul 2017 13:18:58 +0000 (15:18 +0200)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 28 Jul 2017 13:18:58 +0000 (15:18 +0200)
1  2 
src/jalview/appletgui/SeqPanel.java
src/jalview/gui/SeqPanel.java

@@@ -54,8 -54,10 +54,8 @@@ import java.awt.event.InputEvent
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
  import java.awt.event.MouseMotionListener;
 -import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
 -import java.util.ListIterator;
  import java.util.Vector;
  
  public class SeqPanel extends Panel implements MouseMotionListener,
      }
  
      int seq = findSeq(evt);
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
  
      if (seq < 0 || res < 0)
      {
          av.setSelectionGroup(null);
        }
  
 -      int column = findRes(evt);
 -      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
 -      List<SequenceFeature> features = findFeaturesAtRes(sequence,
 -              sequence.findPosition(column));
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(features, column + 1, sequence);
 -      }
 +      int column = findColumn(evt);
 +      List<SequenceFeature> features = findFeaturesAtColumn(sequence,
 +              column + 1);
  
        if (!features.isEmpty())
        {
          seqCanvas.highlightSearchResults(highlight);
          seqCanvas.getFeatureRenderer().amendFeatures(
                  Collections.singletonList(sequence), features, false, ap);
 -
 -        seqCanvas.highlightSearchResults(null);
 +        av.setSearchResults(null); // clear highlighting
 +        seqCanvas.repaint(); // draw new/amended features
        }
      }
    }
  
    int wrappedBlock = -1;
  
 -  int findRes(MouseEvent evt)
 +  /**
 +   * Returns the aligned sequence position (base 0) at the mouse position, or
 +   * the closest visible one
 +   * 
 +   * @param evt
 +   * @return
 +   */
 +  int findColumn(MouseEvent evt)
    {
      int res = 0;
      int x = evt.getX();
        wrappedBlock += startRes / cwidth;
        int startOffset = startRes % cwidth; // in case start is scrolled right
                                             // from 0
-       res = wrappedBlock * cwidth
-               + Math.min(cwidth - 1, startOffset + x / av.getCharWidth());
+       res = wrappedBlock * cwidth + startOffset
+               + +Math.min(cwidth - 1, x / av.getCharWidth());
      }
      else
      {
    {
  
      int seq = findSeq(evt);
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
  
      if (seq < av.getAlignment().getHeight()
              && res < av.getAlignment().getSequenceAt(seq).getLength())
    @Override
    public void mouseMoved(MouseEvent evt)
    {
 -    final int column = findRes(evt);
 +    final int column = findColumn(evt);
      int seq = findSeq(evt);
  
      if (seq >= av.getAlignment().getHeight() || seq < 0 || column < 0)
       */
      if (av.isShowSequenceFeatures())
      {
 -      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
 -              sequence.findPosition(column));
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(allFeatures, column + 1, sequence);
 -      }
 +      List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
 +              column + 1);
        for (SequenceFeature sf : allFeatures)
        {
          tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
    }
  
    /**
 -   * Removes from the list of features any that start after, or end before, the
 -   * given column position. This allows us to retain only those features
 -   * adjacent to a gapped position that straddle the position. Contact features
 -   * that 'straddle' the position are also removed, since they are not 'at' the
 -   * position.
 +   * Returns features at the specified aligned column on the given sequence.
 +   * Non-positional features are not included. If the column has a gap, then
 +   * enclosing features are included (but not contact features).
     * 
 -   * @param features
 -   * @param column
 -   *          alignment column (1..)
     * @param sequence
 +   * @param column
 +   *          (1..)
 +   * @return
     */
 -  protected void removeAdjacentFeatures(List<SequenceFeature> features,
 -          int column, SequenceI sequence)
 -  {
 -    // TODO should this be an AlignViewController method (shared by gui)?
 -    ListIterator<SequenceFeature> it = features.listIterator();
 -    while (it.hasNext())
 -    {
 -      SequenceFeature sf = it.next();
 -      if (sf.isContactFeature()
 -              || sequence.findIndex(sf.getBegin()) > column
 -              || sequence.findIndex(sf.getEnd()) < column)
 -      {
 -        it.remove();
 -      }
 -    }
 -  }
 -
 -  List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
 +  List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
    {
 -    List<SequenceFeature> result = new ArrayList<>();
 -    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))
 -        {
 -          result.add(features[i]);
 -        }
 -      }
 -    }
 -
 -    return result;
 +    return seqCanvas.getFeatureRenderer().findFeaturesAtColumn(sequence, column);
    }
  
    Tooltip tooltip;
        return;
      }
  
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
  
      if (res < 0)
      {
          // Find the next gap before the end
          // of the visible region boundary
          boolean blank = false;
 -        for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
 +        for (; fixedRight > lastres; fixedRight--)
          {
            blank = true;
  
        scrollThread = null;
      }
  
 -    int res = findRes(evt);
 +    int column = findColumn(evt);
      int seq = findSeq(evt);
      oldSeq = seq;
      startWrapBlock = wrappedBlock;
  
      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
  
 -    if (sequence == null || res > sequence.getLength())
 +    if (sequence == null || column > sequence.getLength())
      {
        return;
      }
  
      stretchGroup = av.getSelectionGroup();
  
 -    if (stretchGroup == null || !stretchGroup.contains(sequence, res))
 +    if (stretchGroup == null || !stretchGroup.contains(sequence, column))
      {
 -      stretchGroup = av.getAlignment().findGroup(sequence, res);
 +      stretchGroup = av.getAlignment().findGroup(sequence, column);
        if (stretchGroup != null)
        {
          // only update the current selection if the popup menu has a group to
      // DETECT RIGHT MOUSE BUTTON IN AWT
      if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
      {
 -      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
 -              sequence.findPosition(res));
 +      List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
 +              sequence.findPosition(column + 1));
  
        Vector<String> links = null;
        for (SequenceFeature sf : allFeatures)
          {
            if (links == null)
            {
 -            links = new Vector<>();
 -          }
 -          for (int j = 0; j < sf.links.size(); j++)
 -          {
 -            links.addElement(sf.links.elementAt(j));
 +            links = new Vector<String>();
            }
 +          links.addAll(sf.links);
          }
        }
        APopupMenu popup = new APopupMenu(ap, null, links);
  
      if (av.cursorMode)
      {
 -      seqCanvas.cursorX = findRes(evt);
 +      seqCanvas.cursorX = findColumn(evt);
        seqCanvas.cursorY = findSeq(evt);
        seqCanvas.repaint();
        return;
      {
        // define a new group here
        SequenceGroup sg = new SequenceGroup();
 -      sg.setStartRes(res);
 -      sg.setEndRes(res);
 +      sg.setStartRes(column);
 +      sg.setEndRes(column);
        sg.addSequence(sequence, false);
        av.setSelectionGroup(sg);
        stretchGroup = sg;
  
    public void doMouseDraggedDefineMode(MouseEvent evt)
    {
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
      int y = findSeq(evt);
  
      if (wrappedBlock != startWrapBlock)
@@@ -62,6 -62,7 +62,6 @@@ import java.awt.event.MouseWheelListene
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
 -import java.util.ListIterator;
  
  import javax.swing.JPanel;
  import javax.swing.SwingUtilities;
@@@ -84,16 -85,6 +84,16 @@@ public class SeqPanel extends JPanel im
    /** DOCUMENT ME!! */
    public AlignmentPanel ap;
  
 +  /*
 +   * last column position for mouseMoved event
 +   */
 +  private int lastMouseColumn;
 +
 +  /*
 +   * last sequence offset for mouseMoved event
 +   */
 +  private int lastMouseSeq;
 +
    protected int lastres;
  
    protected int startseq;
        ssm.addStructureViewerListener(this);
        ssm.addSelectionListener(this);
      }
 +
 +    lastMouseColumn = -1;
 +    lastMouseSeq = -1;
    }
  
    int startWrapBlock = -1;
  
        int y = evt.getY();
        y -= hgap;
 -      x = Math.max(0, x - seqCanvas.LABEL_WEST);
 +      x = Math.max(0, x - seqCanvas.labelWidthWest);
  
        int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
        if (cwidth < 1)
        wrappedBlock += startRes / cwidth;
        // allow for wrapped view scrolled right (possible from Overview)
        int startOffset = startRes % cwidth;
-       res = wrappedBlock * cwidth
-               + Math.min(cwidth - 1, startOffset + x / av.getCharWidth());
+       res = wrappedBlock * cwidth + startOffset
+               + +Math.min(cwidth - 1, x / av.getCharWidth());
      }
      else
      {
      }
      lastSearchResults = results;
  
 +    boolean wasScrolled = false;
 +
      if (av.isFollowHighlight())
      {
        // don't allow highlight of protein/cDNA to also scroll a complementary
        // over residue to change abruptly, causing highlighted residue in panel 2
        // to change, causing a scroll in panel 1 etc)
        ap.setToScrollComplementPanel(false);
 -      if (ap.scrollToPosition(results, false))
 +      wasScrolled = ap.scrollToPosition(results, false);
 +      if (wasScrolled)
        {
          seqCanvas.revalidate();
        }
        ap.setToScrollComplementPanel(true);
      }
 -    setStatusMessage(results);
 -    seqCanvas.highlightSearchResults(results);
 +
 +    boolean noFastPaint = wasScrolled && av.getWrapAlignment();
 +    if (seqCanvas.highlightSearchResults(results, noFastPaint))
 +    {
 +      setStatusMessage(results);
 +    }
    }
  
    @Override
      int seq = findSeq(evt);
      if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
      {
 +      lastMouseSeq = -1;
        return;
      }
 +    if (column == lastMouseColumn && seq == lastMouseSeq)
 +    {
 +      /*
 +       * just a pixel move without change of residue
 +       */
 +      return;
 +    }
 +    lastMouseColumn = column;
 +    lastMouseSeq = seq;
  
      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
  
      if (av.isShowSequenceFeatures())
      {
        List<SequenceFeature> features = ap.getFeatureRenderer()
 -              .findFeaturesAtRes(sequence.getDatasetSequence(), pos);
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(features, column + 1, sequence);
 -      }
 +              .findFeaturesAtColumn(sequence, column + 1);
        seqARep.appendFeatures(tooltipText, pos, features,
                this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
      }
      }
      else
      {
 -      if (lastTooltip == null
 -              || !lastTooltip.equals(tooltipText.toString()))
 +      String textString = tooltipText.toString();
 +      if (lastTooltip == null || !lastTooltip.equals(textString))
        {
 -        String formatedTooltipText = JvSwingUtils.wrapTooltip(true,
 -                tooltipText.toString());
 -        // String formatedTooltipText = tooltipText.toString();
 -        setToolTipText(formatedTooltipText);
 -        lastTooltip = tooltipText.toString();
 -      }
 -
 -    }
 -
 -  }
 -
 -  /**
 -   * Removes from the list of features any that start after, or end before, the
 -   * given column position. This allows us to retain only those features
 -   * adjacent to a gapped position that straddle the position. Contact features
 -   * that 'straddle' the position are also removed, since they are not 'at' the
 -   * position.
 -   * 
 -   * @param features
 -   * @param column
 -   *          alignment column (1..)
 -   * @param sequence
 -   */
 -  protected void removeAdjacentFeatures(List<SequenceFeature> features,
 -          final int column, SequenceI sequence)
 -  {
 -    // TODO should this be an AlignViewController method (and reused by applet)?
 -    ListIterator<SequenceFeature> it = features.listIterator();
 -    while (it.hasNext())
 -    {
 -      SequenceFeature sf = it.next();
 -      if (sf.isContactFeature()
 -              || sequence.findIndex(sf.getBegin()) > column
 -              || sequence.findIndex(sf.getEnd()) < column)
 -      {
 -        it.remove();
 +        String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
 +                textString);
 +        setToolTipText(formattedTooltipText);
 +        lastTooltip = textString;
        }
      }
    }
     *          aligned sequence object
     * @param column
     *          alignment column
 -   * @param seq
 +   * @param seqIndex
     *          index of sequence in alignment
     * @return sequence position of residue at column, or adjacent residue if at a
     *         gap
     */
 -  int setStatusMessage(SequenceI sequence, final int column, int seq)
 +  int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
 +  {
 +    char sequenceChar = sequence.getCharAt(column);
 +    int pos = sequence.findPosition(column);
 +    setStatusMessage(sequence, seqIndex, sequenceChar, pos);
 +
 +    return pos;
 +  }
 +
 +  /**
 +   * Builds the status message for the current cursor location and writes it to
 +   * the status bar, for example
 +   * 
 +   * <pre>
 +   * Sequence 3 ID: FER1_SOLLC
 +   * Sequence 5 ID: FER1_PEA Residue: THR (4)
 +   * Sequence 5 ID: FER1_PEA Residue: B (3)
 +   * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
 +   * </pre>
 +   * 
 +   * @param sequence
 +   * @param seqIndex
 +   *          sequence position in the alignment (1..)
 +   * @param sequenceChar
 +   *          the character under the cursor
 +   * @param residuePos
 +   *          the sequence residue position (if not over a gap)
 +   */
 +  protected void setStatusMessage(SequenceI sequence, int seqIndex,
 +          char sequenceChar, int residuePos)
    {
      StringBuilder text = new StringBuilder(32);
  
      /*
       * Sequence number (if known), and sequence name.
       */
 -    String seqno = seq == -1 ? "" : " " + (seq + 1);
 +    String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
      text.append("Sequence").append(seqno).append(" ID: ")
              .append(sequence.getName());
  
      /*
       * Try to translate the display character to residue name (null for gap).
       */
 -    final String displayChar = String.valueOf(sequence.getCharAt(column));
 -    boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
 -    int pos = sequence.findPosition(column);
 +    boolean isGapped = Comparison.isGap(sequenceChar);
  
      if (!isGapped)
      {
        boolean nucleotide = av.getAlignment().isNucleotide();
 +      String displayChar = String.valueOf(sequenceChar);
        if (nucleotide)
        {
          residue = ResidueProperties.nucleotideName.get(displayChar);
        text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
                .append(": ").append(residue == null ? displayChar : residue);
  
 -      text.append(" (").append(Integer.toString(pos)).append(")");
 +      text.append(" (").append(Integer.toString(residuePos)).append(")");
      }
      ap.alignFrame.statusBar.setText(text.toString());
 -
 -    return pos;
    }
  
    /**
  
        if (seq == ds)
        {
 -        /*
 -         * Convert position in sequence (base 1) to sequence character array
 -         * index (base 0)
 -         */
 -        int start = m.getStart() - m.getSequence().getStart();
 -        setStatusMessage(seq, start, sequenceIndex);
 +        int start = m.getStart();
 +        setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
 +                start);
          return;
        }
      }
        }
  
        int column = findColumn(evt);
 -      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
  
        /*
         * find features at the position (if not gapped), or straddling
         * the position (if at a gap)
         */
        List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
 -              .findFeaturesAtRes(sequence.getDatasetSequence(),
 -                      sequence.findPosition(column));
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(features, column, sequence);
 -      }
 +              .findFeaturesAtColumn(sequence, column + 1);
  
        if (!features.isEmpty())
        {
          SearchResultsI highlight = new SearchResults();
          highlight.addResult(sequence, features.get(0).getBegin(), features
                  .get(0).getEnd());
 -        seqCanvas.highlightSearchResults(highlight);
 +        seqCanvas.highlightSearchResults(highlight, false);
  
          /*
           * open the Amend Features dialog; clear highlighting afterwards,
          List<SequenceI> seqs = Collections.singletonList(sequence);
          seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
                  ap);
 -        seqCanvas.highlightSearchResults(null);
 +        av.setSearchResults(null); // clear highlighting
 +        seqCanvas.repaint(); // draw new/amended features
        }
      }
    }
     */
    void showPopupMenu(MouseEvent evt)
    {
 -    final int res = findColumn(evt);
 +    final int column = findColumn(evt);
      final int seq = findSeq(evt);
      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
      List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
 -            .findFeaturesAtRes(sequence.getDatasetSequence(),
 -                    sequence.findPosition(res));
 +            .findFeaturesAtColumn(sequence, column + 1);
      List<String> links = new ArrayList<>();
      for (SequenceFeature sf : allFeatures)
      {