Merge remote-tracking branch 'origin/bug/JAL-2839findWithHidden' into merge/JAL-2839_2933
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 5 Mar 2019 12:40:08 +0000 (12:40 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 5 Mar 2019 12:40:08 +0000 (12:40 +0000)
1  2 
src/jalview/datamodel/SearchResults.java
src/jalview/datamodel/SequenceI.java
src/jalview/gui/IdPanel.java
test/jalview/controller/AlignViewControllerTest.java

@@@ -91,27 -91,18 +91,18 @@@ public class SearchResults implements S
        }
      }
  
-     /* (non-Javadoc)
-      * @see jalview.datamodel.SearchResultMatchI#getSequence()
-      */
      @Override
      public SequenceI getSequence()
      {
        return sequence;
      }
  
-     /* (non-Javadoc)
-      * @see jalview.datamodel.SearchResultMatchI#getStart()
-      */
      @Override
      public int getStart()
      {
        return start;
      }
  
-     /* (non-Javadoc)
-      * @see jalview.datamodel.SearchResultMatchI#getEnd()
-      */
      @Override
      public int getEnd()
      {
        return (sequence == m.getSequence() && start == m.getStart()
                && end == m.getEnd());
      }
+     @Override
+     public boolean contains(SequenceI seq, int from, int to)
+     {
+       return (sequence == seq && start <= from && end >= to);
+     }
    }
  
-   /* (non-Javadoc)
-    * @see jalview.datamodel.SearchResultsI#addResult(jalview.datamodel.SequenceI, int, int)
-    */
    @Override
    public SearchResultMatchI addResult(SequenceI seq, int start, int end)
    {
      Match m = new Match(seq, start, end);
-     matches.add(m);
+     if (!matches.contains(m))
+     {
+       matches.add(m);
+     }
      return m;
    }
  
-   /* (non-Javadoc)
-    * @see jalview.datamodel.SearchResultsI#involvesSequence(jalview.datamodel.SequenceI)
-    */
    @Override
    public boolean involvesSequence(SequenceI sequence)
    {
      return false;
    }
  
-   /* (non-Javadoc)
-    * @see jalview.datamodel.SearchResultsI#getResults(jalview.datamodel.SequenceI, int, int)
-    */
    @Override
    public int[] getResults(SequenceI sequence, int start, int end)
    {
    {
      int count = 0;
      BitSet mask = new BitSet();
 +    int startRes = sqcol.getStartRes();
 +    int endRes = sqcol.getEndRes();
 +
      for (SequenceI s : sqcol.getSequences())
      {
 -      int[] cols = getResults(s, sqcol.getStartRes(), sqcol.getEndRes());
 +      int[] cols = getResults(s, startRes, endRes);
        if (cols != null)
        {
          for (int pair = 0; pair < cols.length; pair += 2)
      return count;
    }
  
-   /* (non-Javadoc)
-    * @see jalview.datamodel.SearchResultsI#getSize()
-    */
    @Override
    public int getSize()
    {
      return matches.size();
    }
  
-   /* (non-Javadoc)
-    * @see jalview.datamodel.SearchResultsI#isEmpty()
-    */
    @Override
    public boolean isEmpty()
    {
      return matches.isEmpty();
    }
  
-   /* (non-Javadoc)
-    * @see jalview.datamodel.SearchResultsI#getResults()
-    */
    @Override
    public List<SearchResultMatchI> getResults()
    {
@@@ -112,9 -112,11 +112,11 @@@ public interface SequenceI extends ASeq
     * get a range on the sequence as a string
     * 
     * @param start
-    *          position relative to start of sequence including gaps (from 0)
+    *          (inclusive) position relative to start of sequence including gaps
+    *          (from 0)
     * @param end
-    *          position relative to start of sequence including gaps (from 0)
+    *          (exclusive) position relative to start of sequence including gaps
+    *          (from 0)
     * 
     * @return String containing all gap and symbols in specified range
     */
    public int findPosition(int i);
  
    /**
 -   * Returns the from-to sequence positions (start..) for the given column
 -   * positions (1..), or null if no residues are included in the range
 +   * Returns the sequence positions for first and last residues lying within the
 +   * given column positions [fromColum,toColumn] (where columns are numbered
 +   * from 1), or null if no residues are included in the range
     * 
     * @param fromColum
 +   *          - first column base 1
     * @param toColumn
 +   *          - last column, base 1
     * @return
     */
 -  public Range findPositions(int fromColum, int toColumn);
 +  public ContiguousI findPositions(int fromColum, int toColumn);
  
    /**
     * Returns an int array where indices correspond to each residue in the
   */
  package jalview.gui;
  
 +import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
 +import jalview.gui.SeqPanel.MousePos;
  import jalview.io.SequenceAnnotationReport;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.viewmodel.AlignmentViewport;
 +import jalview.viewmodel.ViewportRanges;
  
  import java.awt.BorderLayout;
  import java.awt.event.MouseEvent;
@@@ -41,7 -38,6 +41,7 @@@ import java.awt.event.MouseWheelListene
  import java.util.List;
  
  import javax.swing.JPanel;
 +import javax.swing.JPopupMenu;
  import javax.swing.SwingUtilities;
  import javax.swing.ToolTipManager;
  
@@@ -96,46 -92,25 +96,46 @@@ public class IdPanel extends JPane
    }
  
    /**
 -   * Respond to mouse movement by constructing tooltip text for the sequence id
 -   * under the mouse.
 +   * Responds to mouse movement by setting tooltip text for the sequence id
 +   * under the mouse (or possibly annotation label, when in wrapped mode)
     * 
     * @param e
 -   *          DOCUMENT ME!
     */
    @Override
    public void mouseMoved(MouseEvent e)
    {
      SeqPanel sp = alignPanel.getSeqPanel();
 -    int seq = Math.max(0, sp.findSeq(e));
 -    if (seq > -1 && seq < av.getAlignment().getHeight())
 +    MousePos pos = sp.findMousePosition(e);
 +    if (pos.isOverAnnotation())
 +    {
 +      /*
 +       * mouse is over an annotation label in wrapped mode
 +       */
 +      AlignmentAnnotation[] anns = av.getAlignment()
 +              .getAlignmentAnnotation();
 +      AlignmentAnnotation annotation = anns[pos.annotationIndex];
 +      setToolTipText(AnnotationLabels.getTooltip(annotation));
 +      alignPanel.alignFrame.setStatus(
 +              AnnotationLabels.getStatusMessage(annotation, anns));
 +    }
 +    else
      {
 -      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
 -      StringBuilder tip = new StringBuilder(64);
 -      seqAnnotReport.createTooltipAnnotationReport(tip, sequence,
 -              av.isShowDBRefs(), av.isShowNPFeats(), sp.seqCanvas.fr);
 -      setToolTipText(JvSwingUtils.wrapTooltip(true,
 -              sequence.getDisplayId(true) + " " + tip.toString()));
 +      int seq = Math.max(0, pos.seqIndex);
 +      if (seq < av.getAlignment().getHeight())
 +      {
 +        SequenceI sequence = av.getAlignment().getSequenceAt(seq);
 +        StringBuilder tip = new StringBuilder(64);
 +        tip.append(sequence.getDisplayId(true)).append(" ");
 +        seqAnnotReport.createTooltipAnnotationReport(tip, sequence,
 +                av.isShowDBRefs(), av.isShowNPFeats(), sp.seqCanvas.fr);
 +        setToolTipText(JvSwingUtils.wrapTooltip(true, tip.toString()));
 +
 +        StringBuilder text = new StringBuilder();
 +        text.append("Sequence ").append(String.valueOf(seq + 1))
 +                .append(" ID: ")
 +                .append(sequence.getName());
 +        alignPanel.alignFrame.setStatus(text.toString());
 +      }
      }
    }
  
    {
      mouseDragging = true;
  
 -    int seq = Math.max(0, alignPanel.getSeqPanel().findSeq(e));
 +    MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
 +    if (pos.isOverAnnotation())
 +    {
 +      // mouse is over annotation label in wrapped mode
 +      return;
 +    }
 +
 +    int seq = Math.max(0, pos.seqIndex);
  
      if (seq < lastid)
      {
        return;
      }
  
 -    int seq = alignPanel.getSeqPanel().findSeq(e);
 +    MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
 +    int seq = pos.seqIndex;
 +    if (pos.isOverAnnotation() || seq < 0)
 +    {
 +      return;
 +    }
 +
      String id = av.getAlignment().getSequenceAt(seq).getName();
      String url = Preferences.sequenceUrlLinks.getPrimaryUrl(id);
  
    {
      if (scrollThread != null)
      {
 -      scrollThread.running = false;
 +      scrollThread.stopScrolling();
      }
    }
  
        return;
      }
  
 +    MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
 +    
      if (e.isPopupTrigger()) // Mac reports this in mousePressed
      {
 -      showPopupMenu(e);
 +      showPopupMenu(e, pos);
        return;
      }
  
        av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
      }
  
 -    int seq = alignPanel.getSeqPanel().findSeq(e);
      if (e.isShiftDown() && (lastid != -1))
      {
 -      selectSeqs(lastid, seq);
 +      selectSeqs(lastid, pos.seqIndex);
      }
      else
      {
 -      selectSeq(seq);
 +      selectSeq(pos.seqIndex);
      }
  
      av.isSelectionGroupChanged(true);
     * 
     * @param e
     */
 -  void showPopupMenu(MouseEvent e)
 +  void showPopupMenu(MouseEvent e, MousePos pos)
    {
 -    int seq2 = alignPanel.getSeqPanel().findSeq(e);
 -    Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq2);
 +    if (pos.isOverAnnotation())
 +    {
 +      showAnnotationMenu(e, pos);
 +      return;
 +    }
 +
 +    Sequence sq = (Sequence) av.getAlignment().getSequenceAt(pos.seqIndex);
  
      /*
       *  build a new links menu based on the current links
       *  and any non-positional features
       */
 +    List<SequenceFeature> features = null;
 +    if (sq != null)
 +    {
      List<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
 -    List<SequenceFeature> features = sq.getFeatures().getNonPositionalFeatures();
 +      features = sq.getFeatures().getNonPositionalFeatures();
      for (SequenceFeature sf : features)
      {
        if (sf.links != null)
        {
 -        for (String link : sf.links)
 -        {
 -          nlinks.add(link);
 -        }
 +        nlinks.addAll(sf.links);
        }
      }
 +    }
  
      PopupMenu pop = new PopupMenu(alignPanel, sq, features,
              Preferences.getGroupURLLinks());
    }
  
    /**
 +   * On right mouse click on a Consensus annotation label, shows a limited popup
 +   * menu, with options to configure the consensus calculation and rendering.
 +   * 
 +   * @param e
 +   * @param pos
 +   * @see AnnotationLabels#showPopupMenu(MouseEvent)
 +   */
 +  void showAnnotationMenu(MouseEvent e, MousePos pos)
 +  {
 +    if (pos.annotationIndex == -1)
 +    {
 +      return;
 +    }
 +    AlignmentAnnotation[] anns = this.av.getAlignment()
 +            .getAlignmentAnnotation();
 +    if (anns == null || pos.annotationIndex >= anns.length)
 +    {
 +      return;
 +    }
 +    AlignmentAnnotation ann = anns[pos.annotationIndex];
 +    if (!ann.label.contains("Consensus"))
 +    {
 +      return;
 +    }
 +
 +    JPopupMenu pop = new JPopupMenu(
 +            MessageManager.getString("label.annotations"));
 +    AnnotationLabels.addConsensusMenuOptions(this.alignPanel, ann, pop);
 +    pop.show(this, e.getX(), e.getY());
 +  }
 +
 +  /**
     * Toggle whether the sequence is part of the current selection group.
     * 
     * @param seq
      lastid = seq;
  
      SequenceI pickedSeq = av.getAlignment().getSequenceAt(seq);
 -    av.getSelectionGroup().addOrRemove(pickedSeq, true);
 +    av.getSelectionGroup().addOrRemove(pickedSeq, false);
    }
  
    /**
      for (int i = start; i <= end; i++)
      {
        av.getSelectionGroup().addSequence(av.getAlignment().getSequenceAt(i),
 -              i == end);
 +              false);
      }
    }
  
    {
      if (scrollThread != null)
      {
 -      scrollThread.running = false;
 +      scrollThread.stopScrolling();
      }
 +    MousePos pos = alignPanel.getSeqPanel().findMousePosition(e);
  
      mouseDragging = false;
      PaintRefresher.Refresh(this, av.getSequenceSetId());
  
      if (e.isPopupTrigger()) // Windows reports this in mouseReleased
      {
 -      showPopupMenu(e);
 +      showPopupMenu(e, pos);
      }
    }
  
    {
      getIdCanvas().setHighlighted(list);
  
-     if (list == null)
+     if (list == null || list.isEmpty())
      {
        return;
      }
      this.idCanvas = idCanvas;
    }
  
 -  // this class allows scrolling off the bottom of the visible alignment
 +  /**
 +   * Performs scrolling of the visible alignment up or down, adding newly
 +   * visible sequences to the current selection
 +   */
    class ScrollThread extends Thread
    {
 -    boolean running = false;
 +    private boolean running = false;
  
 -    boolean up = true;
 +    private boolean up;
  
 +    /**
 +     * Constructor for a thread that scrolls either up or down
 +     * 
 +     * @param up
 +     */
      public ScrollThread(boolean up)
      {
        this.up = up;
 +      setName("IdPanel$ScrollThread$" + String.valueOf(up));
        start();
      }
  
 +    /**
 +     * Sets a flag to stop the scrolling
 +     */
      public void stopScrolling()
      {
        running = false;
      }
  
 +    /**
 +     * Scrolls the alignment either up or down, one row at a time, adding newly
 +     * visible sequences to the current selection. Speed is limited to a maximum
 +     * of ten rows per second. The thread exits when the end of the alignment is
 +     * reached or a flag is set to stop it.
 +     */
      @Override
      public void run()
      {
  
        while (running)
        {
 -        if (av.getRanges().scrollUp(up))
 +        ViewportRanges ranges = IdPanel.this.av.getRanges();
 +        if (ranges.scrollUp(up))
          {
 -          // scroll was ok, so add new sequence to selection
 -          int seq = av.getRanges().getStartSeq();
 -
 -          if (!up)
 -          {
 -            seq = av.getRanges().getEndSeq();
 -          }
 -
 -          if (seq < lastid)
 -          {
 -            selectSeqs(lastid - 1, seq);
 -          }
 -          else if (seq > lastid)
 -          {
 -            selectSeqs(lastid + 1, seq);
 -          }
 -
 -          lastid = seq;
 +          int toSeq = up ? ranges.getStartSeq() : ranges.getEndSeq();
 +          int fromSeq = toSeq < lastid ? lastid - 1 : lastid + 1;
 +          IdPanel.this.selectSeqs(fromSeq, toSeq);
 +
 +          lastid = toSeq;
          }
          else
          {
 +          /*
 +           * scroll did nothing - reached limit of visible alignment
 +           */
            running = false;
          }
  
@@@ -26,6 -26,7 +26,7 @@@ import static org.testng.AssertJUnit.as
  import jalview.analysis.Finder;
  import jalview.api.AlignViewControllerI;
  import jalview.api.FeatureColourI;
+ import jalview.api.FinderI;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.SearchResults;
  import jalview.datamodel.SearchResultsI;
@@@ -147,8 -148,7 +148,8 @@@ public class AlignViewControllerTes
       * seq1 feature in columns 4-6 is hidden
       * seq2 feature in columns 6-7 is shown
       */
 -    FeatureColourI fc = new FeatureColour(Color.red, Color.blue, 0f, 10f);
 +    FeatureColourI fc = new FeatureColour(null, Color.red, Color.blue, null,
 +            0f, 10f);
      fc.setAboveThreshold(true);
      fc.setThreshold(5f);
      af.getFeatureRenderer().setColour("Metal", fc);
      /*
       *  test Match/Find works first
       */
-     Finder f = new Finder(af.getViewport().getAlignment(), null);
-     f.setFindAll(true);
-     f.setCaseSensitive(true);
-     f.find("M+");
+     FinderI f = new Finder(af.getViewport());
+     f.findAll("M+", true, false);
      assertEquals(
              "Finder found different set of results to manually created SearchResults",
              sr, f.getSearchResults());