From: gmungoc Date: Fri, 1 Mar 2019 14:30:03 +0000 (+0000) Subject: Merge branch 'feature/JAL-3093wrappedModeTooltips' into X-Git-Tag: Release_2_11_0~17^2~83 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=f36251d568f123f14631d4839362355a058dc673;p=jalview.git Merge branch 'feature/JAL-3093wrappedModeTooltips' into merge/JAL-3093_JAL-3132 Conflicts: src/jalview/gui/SeqPanel.java test/jalview/gui/SeqPanelTest.java --- f36251d568f123f14631d4839362355a058dc673 diff --cc src/jalview/gui/IdPanel.java index 3888a74,e29330e..1fd9e49 --- a/src/jalview/gui/IdPanel.java +++ b/src/jalview/gui/IdPanel.java @@@ -339,13 -379,9 +382,10 @@@ public class IdPanel extends JPane { 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()); diff --cc src/jalview/gui/SeqPanel.java index 7292499,a53edb9..7abbd7d --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@@ -83,18 -160,13 +160,13 @@@ public class SeqPanel extends JPane public AlignmentPanel ap; /* - * last column position for mouseMoved event - */ - private int lastMouseColumn; - - /* - * last sequence offset for mouseMoved event + * last position for mouseMoved event */ - private int lastMouseSeq; + private MousePos lastMousePosition; - protected int lastres; + protected int editLastRes; - protected int startseq; + protected int editStartSeq; protected AlignViewport av; @@@ -1212,44 -1352,47 +1376,60 @@@ } } - StringBuilder message = new StringBuilder(64); ++ StringBuilder message = new StringBuilder(64); // for status bar ++ + /* + * make a name for the edit action, for + * status bar message and Undo/Redo menu + */ + String label = null; if (groupEditing) { - message.append("Edit group:"); - if (editCommand == null) - { - editCommand = new EditCommand( - MessageManager.getString("action.edit_group")); - } ++ message.append("Edit group:"); + label = MessageManager.getString("action.edit_group"); } else { - message.append("Edit sequence: " + seq.getName()); - String label = seq.getName(); ++ message.append("Edit sequence: " + seq.getName()); + 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 })); - } + label = MessageManager.formatMessage("label.edit_params", + new String[] + { label }); + } + + /* + * initialise the edit command if there is not + * already one being extended + */ + if (editCommand == null) + { + editCommand = new EditCommand(label); } + if (insertGap) + { + message.append(" insert "); + } + else + { + message.append(" delete "); + } + - message.append(Math.abs(startres - lastres) + " gaps."); ++ message.append(Math.abs(startres - editLastRes) + " gaps."); + ap.alignFrame.setStatus(message.toString()); + - // Are we editing within a selection group? - if (groupEditing || (sg != null - && sg.getSequences(av.getHiddenRepSequences()).contains(seq))) + /* + * is there a selection group containing the sequence being edited? + * if so the boundary of the group is the limit of the edit + * (but the edit may be inside or outside the selection group) + */ + boolean inSelectionGroup = sg != null + && sg.getSequences(av.getHiddenRepSequences()).contains(seq); + if (groupEditing || inSelectionGroup) { fixedColumns = true; @@@ -1319,54 -1462,6 +1499,54 @@@ } } + boolean success = doEditSequence(insertGap, editSeq, startres, + fixedRight, fixedColumns, sg); + + /* + * report what actually happened (might be less than + * what was requested), by inspecting the edit commands added + */ + String msg = getEditStatusMessage(editCommand); - ap.alignFrame.statusBar.setText(msg == null ? " " : msg); ++ ap.alignFrame.setStatus(msg == null ? " " : msg); + if (!success) + { + endEditing(); + } + + editLastRes = startres; + seqCanvas.repaint(); + } + + /** + * A helper method that performs the requested editing to insert or delete + * gaps (if possible). Answers true if the edit was successful, false if could + * only be performed in part or not at all. Failure may occur in 'locked edit' + * mode, when an insertion requires a matching gapped position (or column) to + * delete, and deletion requires an adjacent gapped position (or column) to + * remove. + * + * @param insertGap + * true if inserting gap(s), false if deleting + * @param editSeq + * (unused parameter, currently always false) + * @param startres + * the column at which to perform the edit + * @param fixedRight + * fixed right boundary column of a locked edit (within or to the + * left of a selection group) + * @param fixedColumns + * true if this is a locked edit + * @param sg + * the sequence group (if group edit is being performed) + * @return + */ + protected boolean doEditSequence(final boolean insertGap, + final boolean editSeq, final int startres, int fixedRight, + final boolean fixedColumns, final SequenceGroup sg) + { + final SequenceI seq = av.getAlignment().getSequenceAt(editStartSeq); + SequenceI[] seqs = new SequenceI[] { seq }; + if (groupEditing) { List vseqs = sg.getSequences(av.getHiddenRepSequences()); diff --cc test/jalview/gui/SeqPanelTest.java index 7f3aef1,d2344cc..f163299 --- a/test/jalview/gui/SeqPanelTest.java +++ b/test/jalview/gui/SeqPanelTest.java @@@ -21,17 -21,25 +21,30 @@@ package jalview.gui; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + import static org.testng.Assert.assertTrue; + import jalview.api.AlignViewportI; + import jalview.bin.Cache; + import jalview.bin.Jalview; +import jalview.commands.EditCommand; +import jalview.commands.EditCommand.Action; +import jalview.commands.EditCommand.Edit; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; + import jalview.gui.SeqPanel.MousePos; + import jalview.io.DataSourceType; + import jalview.io.FileLoader; +import jalview.util.MessageManager; + import java.awt.Event; + import java.awt.event.MouseEvent; + + import javax.swing.JLabel; + + import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@@ -95,107 -107,750 +112,854 @@@ public class SeqPanelTes } @Test(groups = "Functional") + public void testGetEditStatusMessage() + { + assertNull(SeqPanel.getEditStatusMessage(null)); + + EditCommand edit = new EditCommand(); // empty + assertNull(SeqPanel.getEditStatusMessage(edit)); + + SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") }; + + // 1 gap + edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-')); + String expected = MessageManager.formatMessage("label.insert_gap", "1"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 3 more gaps makes +4 + edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 3, '-')); + expected = MessageManager.formatMessage("label.insert_gaps", "4"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 2 deletes makes + 2 + edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-')); + expected = MessageManager.formatMessage("label.insert_gaps", "2"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 2 more deletes makes 0 - no text + edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-')); + assertNull(SeqPanel.getEditStatusMessage(edit)); + + // 1 more delete makes 1 delete + edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-')); + expected = MessageManager.formatMessage("label.delete_gap", "1"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 1 more delete makes 2 deletes + edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-')); + expected = MessageManager.formatMessage("label.delete_gaps", "2"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + } + + /** + * Tests that simulate 'locked editing', where an inserted gap is balanced by + * a gap deletion in the selection group, and vice versa + */ + @Test(groups = "Functional") + public void testGetEditStatusMessage_lockedEditing() + { + EditCommand edit = new EditCommand(); // empty + SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") }; + + // 1 gap inserted, balanced by 1 delete + Edit e1 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-'); + edit.addEdit(e1); + Edit e2 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 1, '-'); + e2.setSystemGenerated(true); + edit.addEdit(e2); + String expected = MessageManager.formatMessage("label.insert_gap", "1"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 2 more gaps makes +3 + Edit e3 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-'); + edit.addEdit(e3); + Edit e4 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 2, '-'); + e4.setSystemGenerated(true); + edit.addEdit(e4); + expected = MessageManager.formatMessage("label.insert_gaps", "3"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 2 deletes makes + 1 + Edit e5 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'); + edit.addEdit(e5); + Edit e6 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-'); + e6.setSystemGenerated(true); + edit.addEdit(e6); + expected = MessageManager.formatMessage("label.insert_gap", "1"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 1 more delete makes 0 - no text + Edit e7 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'); + edit.addEdit(e7); + Edit e8 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-'); + e8.setSystemGenerated(true); + edit.addEdit(e8); + expected = MessageManager.formatMessage("label.insert_gaps", "2"); + assertNull(SeqPanel.getEditStatusMessage(edit)); + + // 1 more delete makes 1 delete + Edit e9 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'); + edit.addEdit(e9); + Edit e10 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-'); + e10.setSystemGenerated(true); + edit.addEdit(e10); + expected = MessageManager.formatMessage("label.delete_gap", "1"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + + // 2 more deletes makes 3 deletes + Edit e11 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'); + edit.addEdit(e11); + Edit e12 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-'); + e12.setSystemGenerated(true); + edit.addEdit(e12); + expected = MessageManager.formatMessage("label.delete_gaps", "3"); + assertEquals(SeqPanel.getEditStatusMessage(edit), expected); + } ++ + public void testFindMousePosition_unwrapped() + { + String seqData = ">Seq1\nAACDE\n>Seq2\nAA--E\n"; + AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(seqData, + DataSourceType.PASTE); + AlignViewportI av = alignFrame.getViewport(); + av.setShowAnnotation(true); + av.setWrapAlignment(false); + final int charHeight = av.getCharHeight(); + final int charWidth = av.getCharWidth(); + // sanity checks: + assertTrue(charHeight > 0); + assertTrue(charWidth > 0); + assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0); + + SeqPanel testee = alignFrame.alignPanel.getSeqPanel(); + int x = 0; + int y = 0; + + /* + * mouse at top left of unwrapped panel + */ + MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, + 0, 0, 0, false, 0); + MousePos pos = testee.findMousePosition(evt); + assertEquals(pos.column, 0); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + } + + @AfterMethod(alwaysRun = true) + public void tearDown() + { + Desktop.instance.closeAll_actionPerformed(null); + } + + @Test(groups = "Functional") + public void testFindMousePosition_wrapped_annotations() + { + Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true"); + Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true"); + AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewportI av = alignFrame.getViewport(); + av.setScaleAboveWrapped(false); + av.setScaleLeftWrapped(false); + av.setScaleRightWrapped(false); + alignFrame.alignPanel.paintAlignment(false, false); + waitForSwing(); // for Swing thread + + final int charHeight = av.getCharHeight(); + final int charWidth = av.getCharWidth(); + final int alignmentHeight = av.getAlignment().getHeight(); + + // sanity checks: + assertTrue(charHeight > 0); + assertTrue(charWidth > 0); + assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0); + + SeqPanel testee = alignFrame.alignPanel.getSeqPanel(); + int x = 0; + int y = 0; + + /* + * mouse at top left of wrapped panel; there is a gap of charHeight + * above the alignment + */ + MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, + 0, 0, 0, false, 0); + MousePos pos = testee.findMousePosition(evt); + assertEquals(pos.column, 0); + assertEquals(pos.seqIndex, -1); // above sequences + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of gap above + */ + y = charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor over top of first sequence + */ + y = charHeight; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of first sequence + */ + y = 2 * charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at top of second sequence + */ + y = 2 * charHeight; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of second sequence + */ + y = 3 * charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of last sequence + */ + y = charHeight * (1 + alignmentHeight) - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor below sequences, in 3-pixel gap above annotations + * method reports index of nearest sequence above + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor still in the gap above annotations, now at the bottom of it + */ + y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2 + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at the top of the first annotation + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 0); // over first annotation + + /* + * cursor at the bottom of the first annotation + */ + y += av.getAlignment().getAlignmentAnnotation()[0].height - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 0); + + /* + * cursor at the top of the second annotation + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 1); + + /* + * cursor at the bottom of the second annotation + */ + y += av.getAlignment().getAlignmentAnnotation()[1].height - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 1); + + /* + * cursor at the top of the third annotation + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 2); + + /* + * cursor at the bottom of the third annotation + */ + y += av.getAlignment().getAlignmentAnnotation()[2].height - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 2); + + /* + * cursor in gap between wrapped widths + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of gap between wrapped widths + */ + y += charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at top of first sequence, second wrapped width + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + } + + @Test(groups = "Functional") + public void testFindMousePosition_wrapped_scaleAbove() + { + Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true"); + Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true"); + AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewportI av = alignFrame.getViewport(); + av.setScaleAboveWrapped(true); + av.setScaleLeftWrapped(false); + av.setScaleRightWrapped(false); + alignFrame.alignPanel.paintAlignment(false, false); + waitForSwing(); + + final int charHeight = av.getCharHeight(); + final int charWidth = av.getCharWidth(); + final int alignmentHeight = av.getAlignment().getHeight(); + + // sanity checks: + assertTrue(charHeight > 0); + assertTrue(charWidth > 0); + assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0); + + SeqPanel testee = alignFrame.alignPanel.getSeqPanel(); + int x = 0; + int y = 0; + + /* + * mouse at top left of wrapped panel; there is a gap of charHeight + * above the alignment + */ + MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, + 0, 0, 0, false, 0); + MousePos pos = testee.findMousePosition(evt); + assertEquals(pos.column, 0); + assertEquals(pos.seqIndex, -1); // above sequences + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of gap above + * two charHeights including scale panel + */ + y = 2 * charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor over top of first sequence + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of first sequence + */ + y += charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at top of second sequence + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of second sequence + */ + y += charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of last sequence + * (scale + gap + sequences) + */ + y = charHeight * (2 + alignmentHeight) - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor below sequences, in 3-pixel gap above annotations + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor still in the gap above annotations, now at the bottom of it + * method reports index of nearest sequence above + */ + y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2 + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at the top of the first annotation + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 0); // over first annotation + + /* + * cursor at the bottom of the first annotation + */ + y += av.getAlignment().getAlignmentAnnotation()[0].height - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 0); + + /* + * cursor at the top of the second annotation + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 1); + + /* + * cursor at the bottom of the second annotation + */ + y += av.getAlignment().getAlignmentAnnotation()[1].height - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 1); + + /* + * cursor at the top of the third annotation + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 2); + + /* + * cursor at the bottom of the third annotation + */ + y += av.getAlignment().getAlignmentAnnotation()[2].height - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, 2); + + /* + * cursor in gap between wrapped widths + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of gap between wrapped widths + */ + y += charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at top of scale, second wrapped width + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of scale, second wrapped width + */ + y += charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at top of first sequence, second wrapped width + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + } + + @Test(groups = "Functional") + public void testFindMousePosition_wrapped_noAnnotations() + { + Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false"); + Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true"); + AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewportI av = alignFrame.getViewport(); + av.setScaleAboveWrapped(false); + av.setScaleLeftWrapped(false); + av.setScaleRightWrapped(false); + alignFrame.alignPanel.paintAlignment(false, false); + waitForSwing(); + + final int charHeight = av.getCharHeight(); + final int charWidth = av.getCharWidth(); + final int alignmentHeight = av.getAlignment().getHeight(); + + // sanity checks: + assertTrue(charHeight > 0); + assertTrue(charWidth > 0); + assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0); + + SeqPanel testee = alignFrame.alignPanel.getSeqPanel(); + int x = 0; + int y = 0; + + /* + * mouse at top left of wrapped panel; there is a gap of charHeight + * above the alignment + */ + MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, + 0, 0, 0, false, 0); + MousePos pos = testee.findMousePosition(evt); + assertEquals(pos.column, 0); + assertEquals(pos.seqIndex, -1); // above sequences + assertEquals(pos.annotationIndex, -1); + + /* + * cursor over top of first sequence + */ + y = charHeight; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at bottom of last sequence + */ + y = charHeight * (1 + alignmentHeight) - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, alignmentHeight - 1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor below sequences, at top of charHeight gap between widths + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor below sequences, at top of charHeight gap between widths + */ + y += charHeight - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, -1); + assertEquals(pos.annotationIndex, -1); + + /* + * cursor at the top of the first sequence, second width + */ + y += 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0, + false, 0); + pos = testee.findMousePosition(evt); + assertEquals(pos.seqIndex, 0); + assertEquals(pos.annotationIndex, -1); + } + + @Test(groups = "Functional") + public void testFindColumn_unwrapped() + { + Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "false"); + AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + SeqPanel testee = alignFrame.alignPanel.getSeqPanel(); + int x = 0; + final int charWidth = alignFrame.getViewport().getCharWidth(); + assertTrue(charWidth > 0); // sanity check + assertEquals(alignFrame.getViewport().getRanges().getStartRes(), 0); + + /* + * mouse at top left of unwrapped panel + */ + MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, + 0, 0, 0, false, 0); + assertEquals(testee.findColumn(evt), 0); + + /* + * not quite one charWidth across + */ + x = charWidth-1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, + 0, 0, 0, false, 0); + assertEquals(testee.findColumn(evt), 0); + + /* + * one charWidth across + */ + x = charWidth; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), 1); + + /* + * two charWidths across + */ + x = 2 * charWidth; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), 2); + + /* + * limited to last column of seqcanvas + */ + x = 20000; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas; + int w = seqCanvas.getWidth(); + // limited to number of whole columns, base 0 + int expected = w / charWidth - 1; + assertEquals(testee.findColumn(evt), expected); + + /* + * hide columns 5-10 (base 1) + */ + alignFrame.getViewport().hideColumns(4, 9); + x = 5 * charWidth + 2; + // x is in 6th visible column, absolute column 12, or 11 base 0 + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), 11); + } + + @Test(groups = "Functional") + public void testFindColumn_wrapped() + { + Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true"); + AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewport av = alignFrame.getViewport(); + av.setScaleAboveWrapped(false); + av.setScaleLeftWrapped(false); + av.setScaleRightWrapped(false); + alignFrame.alignPanel.paintAlignment(false, false); + // need to wait for repaint to finish! + waitForSwing(); + SeqPanel testee = alignFrame.alignPanel.getSeqPanel(); + int x = 0; + final int charWidth = av.getCharWidth(); + assertTrue(charWidth > 0); // sanity check + assertEquals(av.getRanges().getStartRes(), 0); + + /* + * mouse at top left of wrapped panel, no West (left) scale + */ + MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, + 0, 0, 0, false, 0); + assertEquals(testee.findColumn(evt), 0); + + /* + * not quite one charWidth across + */ + x = charWidth-1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, + 0, 0, 0, false, 0); + assertEquals(testee.findColumn(evt), 0); + + /* + * one charWidth across + */ + x = charWidth; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), 1); + + /* + * x over scale left (before drawn columns) results in -1 + */ + av.setScaleLeftWrapped(true); + alignFrame.alignPanel.paintAlignment(false, false); + waitForSwing(); + SeqCanvas seqCanvas = testee.seqCanvas; + int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest"); + assertTrue(labelWidth > 0); + x = labelWidth - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), -1); + + x = labelWidth; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), 0); + + /* + * x over right edge of last residue (including scale left) + */ + int residuesWide = av.getRanges().getViewportWidth(); + assertTrue(residuesWide > 0); + x = labelWidth + charWidth * residuesWide - 1; + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), residuesWide - 1); + + /* + * x over scale right (beyond drawn columns) results in -1 + */ + av.setScaleRightWrapped(true); + alignFrame.alignPanel.paintAlignment(false, false); + waitForSwing(); + labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast"); + assertTrue(labelWidth > 0); + int residuesWide2 = av.getRanges().getViewportWidth(); + assertTrue(residuesWide2 > 0); + assertTrue(residuesWide2 < residuesWide); // available width reduced + x += 1; // just over left edge of scale right + evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0, + false, 0); + assertEquals(testee.findColumn(evt), -1); + + // todo add startRes offset, hidden columns + + } + @BeforeClass(alwaysRun = true) + public static void setUpBeforeClass() throws Exception + { + /* + * use read-only test properties file + */ + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + Jalview.main(new String[] { "-nonews" }); + } + + /** + * waits a few ms for Swing to do something + */ + synchronized void waitForSwing() + { + try + { + super.wait(10); + } catch (InterruptedException e) + { + e.printStackTrace(); + } + } }