From 3828a47f3ee9549412e04d91cbc1455cada45d01 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 19 Aug 2016 16:52:22 +0100 Subject: [PATCH] JAL-2174 bug fixes + refactoring to allow + unit tests --- src/jalview/controller/AlignViewController.java | 265 ++++++++++++-------- src/jalview/gui/FeatureSettings.java | 6 +- .../controller/AlignViewControllerTest.java | 198 +++++++++++++++ 3 files changed, 358 insertions(+), 111 deletions(-) create mode 100644 test/jalview/controller/AlignViewControllerTest.java diff --git a/src/jalview/controller/AlignViewController.java b/src/jalview/controller/AlignViewController.java index 74a5235..f966072 100644 --- a/src/jalview/controller/AlignViewController.java +++ b/src/jalview/controller/AlignViewController.java @@ -169,57 +169,192 @@ public class AlignViewController implements AlignViewControllerI // JBPNote this routine could also mark rows, not just columns. // need a decent query structure to allow all types of feature searches BitSet bs = new BitSet(); - int alw, alStart; - SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null ? viewport - .getAlignment() : viewport.getSelectionGroup()); - alStart = sqcol.getStartRes(); - alw = sqcol.getEndRes() + 1; + SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null || extendCurrent) ? viewport + .getAlignment() : viewport.getSelectionGroup(); + + int nseq = findColumnsWithFeature(featureType, sqcol, bs); + + ColumnSelection cs = viewport.getColumnSelection(); + if (cs == null) + { + cs = new ColumnSelection(); + } + + if (bs.cardinality() > 0 || invert) + { + boolean changed = selectMarkedColumns(cs, invert, extendCurrent, + toggle, bs, sqcol.getStartRes(), sqcol.getEndRes()); + if (changed) + { + viewport.setColumnSelection(cs); + alignPanel.paintAlignment(true); + int columnCount = invert ? (sqcol.getEndRes() - sqcol.getStartRes() + 1) + - bs.cardinality() : bs.cardinality(); + avcg.setStatus(MessageManager.formatMessage( + "label.view_controller_toggled_marked", + new String[] { + MessageManager.getString(toggle ? "label.toggled" + : "label.marked"), + String.valueOf(columnCount), + MessageManager + .getString(invert ? "label.not_containing" + : "label.containing"), + featureType, Integer.valueOf(nseq).toString() })); + return true; + } + } + else + { + avcg.setStatus(MessageManager.formatMessage( + "label.no_feature_of_type_found", + new String[] { featureType })); + if (!extendCurrent && cs != null) + { + cs.clear(); + alignPanel.paintAlignment(true); + } + } + return false; + } + + /** + * Updates the column selection depending on the parameters, and returns true + * if any change was made to the selection. + * + * @param columnSelection + * the current column selection + * @param invert + * if true, deselect marked columns and select unmarked + * @param extendCurrent + * if true, extend rather than replacing the current column selection + * @param toggle + * if true, toggle the selection state of marked columns + * @param markedColumns + * a set identify marked columns + * @param startCol + * the first column of the range to operate over + * @param endCol + * the last column of the range to operate over + * @return + */ + static boolean selectMarkedColumns(ColumnSelection columnSelection, + boolean invert, boolean extendCurrent, boolean toggle, + BitSet markedColumns, int startCol, int endCol) + { + boolean changed = false; + if (!extendCurrent && !toggle) + { + changed = !columnSelection.isEmpty(); + columnSelection.clear(); + } + if (invert) + { + // invert only in the currently selected sequence region + int i = markedColumns.nextClearBit(startCol); + int ibs = markedColumns.nextSetBit(startCol); + while (i >= startCol && i <= endCol) + { + if (ibs < 0 || i < ibs) + { + changed = true; + if (toggle && columnSelection.contains(i)) + { + columnSelection.removeElement(i++); + } + else + { + columnSelection.addElement(i++); + } + } + else + { + i = markedColumns.nextClearBit(ibs); + ibs = markedColumns.nextSetBit(i); + } + } + } + else + { + int i = markedColumns.nextSetBit(startCol); + while (i >= startCol && i <= endCol) + { + changed = true; + if (toggle && columnSelection.contains(i)) + { + columnSelection.removeElement(i); + } + else + { + columnSelection.addElement(i); + } + i = markedColumns.nextSetBit(i + 1); + } + } + return changed; + } + + /** + * Sets a bit in the BitSet for each column in the sequence collection which + * includes the specified feature type. Returns the number of sequences which + * have the feature in the selected range. + * + * @param featureType + * @param sqcol + * @param bs + * @return + */ + static int findColumnsWithFeature(String featureType, + SequenceCollectionI sqcol, + BitSet bs) + { + final int startPosition = sqcol.getStartRes() + 1; // converted to base 1 + final int endPosition = sqcol.getEndRes() + 1; List seqs = sqcol.getSequences(); int nseq = 0; for (SequenceI sq : seqs) { - int tfeat = 0; + boolean sequenceHasFeature = false; if (sq != null) { - SequenceFeature[] sf = sq.getSequenceFeatures(); - if (sf != null) + SequenceFeature[] sfs = sq.getSequenceFeatures(); + if (sfs != null) { int ist = sq.findIndex(sq.getStart()); int iend = sq.findIndex(sq.getEnd()); - if (iend < alStart || ist > alw) + if (iend < startPosition || ist > endPosition) { // sequence not in region continue; } - for (SequenceFeature sfpos : sf) + for (SequenceFeature sf : sfs) { - // future functionalty - featureType == null means mark columns + // future functionality - featureType == null means mark columns // containing all displayed features - if (sfpos != null && (featureType.equals(sfpos.getType()))) + if (sf != null && (featureType.equals(sf.getType()))) { - tfeat++; // optimisation - could consider 'spos,apos' like cursor argument // - findIndex wastes time by starting from first character and // counting - int i = sq.findIndex(sfpos.getBegin()); - int j = sq.findIndex(sfpos.getEnd()); - if (j < alStart || i > alw) + int i = sq.findIndex(sf.getBegin()); + int j = sq.findIndex(sf.getEnd()); + if (j < startPosition || i > endPosition) { // feature is outside selected region continue; } - if (i < alStart) + sequenceHasFeature = true; + if (i < startPosition) { - i = alStart; + i = startPosition; } if (i < ist) { i = ist; } - if (j > alw) + if (j > endPosition) { - j = alw; + j = endPosition; } for (; i <= j; i++) { @@ -229,99 +364,13 @@ public class AlignViewController implements AlignViewControllerI } } - if (tfeat > 0) + if (sequenceHasFeature) { nseq++; } } } - ColumnSelection cs = viewport.getColumnSelection(); - if (bs.cardinality() > 0 || invert) - { - boolean changed = false; - if (cs == null) - { - cs = new ColumnSelection(); - } - else - { - if (!extendCurrent) - { - changed = !cs.isEmpty(); - cs.clear(); - } - } - if (invert) - { - // invert only in the currently selected sequence region - for (int i = bs.nextClearBit(alStart), ibs = bs.nextSetBit(alStart); i >= alStart - && i < (alw);) - { - if (ibs < 0 || i < ibs) - { - changed = true; - if (toggle && cs.contains(i)) - { - cs.removeElement(i++); - } - else - { - cs.addElement(i++); - } - } - else - { - i = bs.nextClearBit(ibs); - ibs = bs.nextSetBit(i); - } - } - } - else - { - for (int i = bs.nextSetBit(alStart); i >= alStart; i = bs - .nextSetBit(i + 1)) - { - changed = true; - if (toggle && cs.contains(i)) - { - cs.removeElement(i); - } - else - { - cs.addElement(i); - } - } - } - if (changed) - { - viewport.setColumnSelection(cs); - alignPanel.paintAlignment(true); - avcg.setStatus(MessageManager.formatMessage( - "label.view_controller_toggled_marked", - new String[] { - MessageManager.getString(toggle ? "label.toggled" - : "label.marked"), - String.valueOf(invert ? alw - alStart - - bs.cardinality() : bs.cardinality()), - MessageManager - .getString(invert ? "label.not_containing" - : "label.containing"), - featureType, Integer.valueOf(nseq).toString() })); - return true; - } - } - else - { - avcg.setStatus(MessageManager.formatMessage( - "label.no_feature_of_type_found", - new String[] { featureType })); - if (!extendCurrent && cs != null) - { - cs.clear(); - alignPanel.paintAlignment(true); - } - } - return false; + return nseq; } @Override diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 742ff8d..3b8ce37 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -174,7 +174,8 @@ public class FeatureSettings extends JPanel implements public void mousePressed(MouseEvent evt) { selectedRow = table.rowAtPoint(evt.getPoint()); - if (SwingUtilities.isRightMouseButton(evt)) + boolean ctrlDown = Platform.isControlDown(evt); + if (SwingUtilities.isRightMouseButton(evt) && !ctrlDown) { popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), table.getValueAt(selectedRow, 1), fr.getMinMax(), @@ -183,9 +184,8 @@ public class FeatureSettings extends JPanel implements else if (evt.getClickCount() == 2) { boolean invertSelection = evt.isAltDown(); - boolean ctrlDown = Platform.isControlDown(evt); boolean toggleSelection = ctrlDown; - boolean extendSelection = evt.isShiftDown() || ctrlDown; + boolean extendSelection = evt.isShiftDown(); fr.ap.alignFrame.avc.markColumnsContainingFeatures( invertSelection, extendSelection, toggleSelection, (String) table.getValueAt(selectedRow, 0)); diff --git a/test/jalview/controller/AlignViewControllerTest.java b/test/jalview/controller/AlignViewControllerTest.java new file mode 100644 index 0000000..d85b68f --- /dev/null +++ b/test/jalview/controller/AlignViewControllerTest.java @@ -0,0 +1,198 @@ +package jalview.controller; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; + +import java.util.BitSet; +import java.util.List; + +import org.testng.annotations.Test; + +public class AlignViewControllerTest +{ + @Test(groups = "Functional") + public void testFindColumnsWithFeature() + { + SequenceI seq1 = new Sequence("seq1", "aMMMaaaaaaaaaaaaaaaa"); + SequenceI seq2 = new Sequence("seq2", "aaaMMMMMMMaaaaaaaaaa"); + SequenceI seq3 = new Sequence("seq3", "aaaaaaaaaaMMMMMaaaaa"); + SequenceI seq4 = new Sequence("seq3", "aaaaaaaaaaaaaaaaaaaa"); + + /* + * features start/end are base 1 + */ + seq1.addSequenceFeature(new SequenceFeature("Metal", "desc", 2, 4, 0f, + null)); + seq1.addSequenceFeature(new SequenceFeature("Helix", "desc", 1, 15, 0f, + null)); + seq2.addSequenceFeature(new SequenceFeature("Metal", "desc", 4, 10, 0f, + null)); + seq3.addSequenceFeature(new SequenceFeature("Metal", "desc", 11, 15, + 0f, null)); + + /* + * select the first three columns --> seq1 2-3 + */ + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(0); // base 0 + sg.setEndRes(2); + sg.addSequence(seq1, false); + sg.addSequence(seq2, false); + sg.addSequence(seq3, false); + sg.addSequence(seq4, false); + + BitSet bs = new BitSet(); + int seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, + bs); + assertEquals(1, seqCount); + assertEquals(2, bs.cardinality()); + assertTrue(bs.get(1)); + assertTrue(bs.get(2)); + + /* + * select the first four columns: seq1 2:4, seq2 4:4 + */ + sg.setEndRes(3); + bs.clear(); + seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, + bs); + assertEquals(2, seqCount); + assertEquals(3, bs.cardinality()); + assertTrue(bs.get(1)); + assertTrue(bs.get(2)); + assertTrue(bs.get(3)); + + /* + * select column 11: seq3 only + */ + sg.setStartRes(10); + sg.setEndRes(10); + bs.clear(); + seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs); + assertEquals(1, seqCount); + assertEquals(1, bs.cardinality()); + assertTrue(bs.get(10)); + + /* + * select columns 16-20: no Metal feature + */ + sg.setStartRes(15); + sg.setEndRes(19); + bs.clear(); + seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs); + assertEquals(0, seqCount); + assertEquals(0, bs.cardinality()); + + /* + * look for a feature that isn't there + */ + sg.setStartRes(0); + sg.setEndRes(19); + bs.clear(); + seqCount = AlignViewController.findColumnsWithFeature("Pfam", sg, bs); + assertEquals(0, seqCount); + assertEquals(0, bs.cardinality()); + } + + @Test(groups = "Functional") + public void testSelectMarkedColumns_invert() + { + ColumnSelection cs = new ColumnSelection(); + cs.addElement(5); // this will be cleared + BitSet toMark = new BitSet(); + toMark.set(1); + toMark.set(3); + toMark.set(6); + toMark.set(9); + + /* + * inverted selection of {3, 6} should select {4, 5, 7, 8} + */ + assertTrue(AlignViewController.selectMarkedColumns(cs, true, false, + false, toMark, 3, 8)); + List selected = cs.getSelected(); + assertEquals(4, selected.size()); + assertTrue(selected.contains(4)); + assertTrue(selected.contains(5)); + assertTrue(selected.contains(7)); + assertTrue(selected.contains(8)); + } + + @Test(groups = "Functional") + public void testSelectMarkedColumns() + { + ColumnSelection cs = new ColumnSelection(); + cs.addElement(5); // this will be cleared + BitSet toMark = new BitSet(); + toMark.set(1); + toMark.set(3); + toMark.set(6); + toMark.set(9); + + assertTrue(AlignViewController.selectMarkedColumns(cs, false, false, + false, toMark, 3, 8)); + List selected = cs.getSelected(); + assertEquals(2, selected.size()); + assertTrue(selected.contains(3)); + assertTrue(selected.contains(6)); + } + + @Test(groups = "Functional") + public void testSelectMarkedColumns_extend() + { + ColumnSelection cs = new ColumnSelection(); + cs.addElement(1); + cs.addElement(5); + BitSet toMark = new BitSet(); + toMark.set(1); + toMark.set(3); + toMark.set(6); + toMark.set(9); + + /* + * extending selection of {3, 6} should leave {1, 3, 5, 6} selected + */ + assertTrue(AlignViewController.selectMarkedColumns(cs, false, true, + false, toMark, 3, 8)); + List selected = cs.getSelected(); + assertEquals(4, selected.size()); + assertTrue(selected.contains(1)); + assertTrue(selected.contains(3)); + assertTrue(selected.contains(5)); + assertTrue(selected.contains(6)); + } + + @Test(groups = "Functional") + public void testSelectMarkedColumns_toggle() + { + ColumnSelection cs = new ColumnSelection(); + cs.addElement(1); // outside change range + cs.addElement(3); + cs.addElement(4); + cs.addElement(10); // outside change range + BitSet toMark = new BitSet(); + toMark.set(1); + toMark.set(3); + toMark.set(6); + toMark.set(9); + + /* + * toggling state of {3, 6} should leave {1, 4, 6, 10} selected + */ + assertTrue(AlignViewController.selectMarkedColumns(cs, false, false, + true, toMark, 3, 8)); + List selected = cs.getSelected(); + assertEquals(4, selected.size()); + assertTrue(selected.contains(1)); + assertTrue(selected.contains(4)); + assertTrue(selected.contains(6)); + assertTrue(selected.contains(10)); + } + // TODO testSelectMarkedColumns with combinations of invert/extend/toggle set +} -- 1.7.10.2