package jalview.gui; import static jalview.gui.FeatureSettings.COLOUR_COLUMN; import static jalview.gui.FeatureSettings.FILTER_COLUMN; import static jalview.gui.FeatureSettings.SHOW_COLUMN; import static jalview.gui.FeatureSettings.TYPE_COLUMN; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.datamodel.features.FeatureMatcher; import jalview.datamodel.features.FeatureMatcherSet; import jalview.datamodel.features.FeatureMatcherSetI; import jalview.io.DataSourceType; import jalview.io.FileFormat; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; import jalview.util.matcher.Condition; import java.awt.Color; import java.io.File; import java.io.IOException; import java.util.HashMap; import org.testng.annotations.Test; public class FeatureSettingsTest { /** * Test a roundtrip of save and reload of feature colours and filters as XML * * @throws IOException */ @Test(groups = "Functional") public void testSaveLoad() throws IOException { AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE, FileFormat.Fasta); SequenceI seq1 = af.getViewport().getAlignment().getSequenceAt(0); /* * add some features to the sequence */ int score = 1; addFeatures(seq1, "type1", score++); addFeatures(seq1, "type2", score++); addFeatures(seq1, "type3", score++); addFeatures(seq1, "type4", score++); addFeatures(seq1, "type5", score++); /* * set colour schemes for features */ FeatureRenderer fr = af.getFeatureRenderer(); // type1: red fr.setColour("type1", new FeatureColour(Color.red)); // type2: by label FeatureColourI byLabel = new FeatureColour(); byLabel.setColourByLabel(true); fr.setColour("type2", byLabel); // type3: by score above threshold FeatureColourI byScore = new FeatureColour(Color.BLACK, Color.BLUE, 1, 10); byScore.setAboveThreshold(true); byScore.setThreshold(2f); fr.setColour("type3", byScore); // type4: by attribute AF FeatureColourI byAF = new FeatureColour(); byAF.setColourByLabel(true); byAF.setAttributeName("AF"); fr.setColour("type4", byAF); // type5: by attribute CSQ:PolyPhen below threshold FeatureColourI byPolyPhen = new FeatureColour(Color.BLACK, Color.BLUE, 1, 10); byPolyPhen.setBelowThreshold(true); byPolyPhen.setThreshold(3f); byPolyPhen.setAttributeName("CSQ", "PolyPhen"); fr.setColour("type5", byPolyPhen); /* * set filters for feature types */ // filter type1 features by (label contains "x") FeatureMatcherSetI filterByX = new FeatureMatcherSet(); filterByX.and(FeatureMatcher.byLabel(Condition.Contains, "x")); fr.setFeatureFilter("type1", filterByX); // filter type2 features by (score <= 2.4 and score > 1.1) FeatureMatcherSetI filterByScore = new FeatureMatcherSet(); filterByScore.and(FeatureMatcher.byScore(Condition.LE, "2.4")); filterByScore.and(FeatureMatcher.byScore(Condition.GT, "1.1")); fr.setFeatureFilter("type2", filterByScore); // filter type3 features by (AF contains X OR CSQ:PolyPhen != 0) FeatureMatcherSetI filterByXY = new FeatureMatcherSet(); filterByXY .and(FeatureMatcher.byAttribute(Condition.Contains, "X", "AF")); filterByXY.or(FeatureMatcher.byAttribute(Condition.NE, "0", "CSQ", "PolyPhen")); fr.setFeatureFilter("type3", filterByXY); /* * save colours and filters to an XML file */ File coloursFile = File.createTempFile("testSaveLoad", ".fc"); coloursFile.deleteOnExit(); FeatureSettings fs = new FeatureSettings(af); fs.save(coloursFile); /* * change feature colours and filters */ FeatureColourI pink = new FeatureColour(Color.pink); fr.setColour("type1", pink); fr.setColour("type2", pink); fr.setColour("type3", pink); fr.setColour("type4", pink); fr.setColour("type5", pink); FeatureMatcherSetI filter2 = new FeatureMatcherSet(); filter2.and(FeatureMatcher.byLabel(Condition.NotContains, "y")); fr.setFeatureFilter("type1", filter2); fr.setFeatureFilter("type2", filter2); fr.setFeatureFilter("type3", filter2); fr.setFeatureFilter("type4", filter2); fr.setFeatureFilter("type5", filter2); /* * reload colours and filters from file and verify they are restored */ fs.load(coloursFile); FeatureColourI fc = fr.getFeatureStyle("type1"); assertTrue(fc.isSimpleColour()); assertEquals(fc.getColour(), Color.red); fc = fr.getFeatureStyle("type2"); assertTrue(fc.isColourByLabel()); fc = fr.getFeatureStyle("type3"); assertTrue(fc.isGraduatedColour()); assertNull(fc.getAttributeName()); assertTrue(fc.isAboveThreshold()); assertEquals(fc.getThreshold(), 2f); fc = fr.getFeatureStyle("type4"); assertTrue(fc.isColourByLabel()); assertTrue(fc.isColourByAttribute()); assertEquals(fc.getAttributeName(), new String[] { "AF" }); fc = fr.getFeatureStyle("type5"); assertTrue(fc.isGraduatedColour()); assertTrue(fc.isColourByAttribute()); assertEquals(fc.getAttributeName(), new String[] { "CSQ", "PolyPhen" }); assertTrue(fc.isBelowThreshold()); assertEquals(fc.getThreshold(), 3f); assertEquals(fr.getFeatureFilter("type1").toStableString(), "Label Contains x"); assertEquals(fr.getFeatureFilter("type2").toStableString(), "(Score LE 2.4) AND (Score GT 1.1)"); assertEquals(fr.getFeatureFilter("type3").toStableString(), "(AF Contains X) OR (CSQ:PolyPhen NE 0.0)"); } /** * Adds two features of the given type to the given sequence, also setting the * score as the value of attribute "AF" and sub-attribute "CSQ:PolyPhen" * * @param seq * @param featureType * @param score */ private void addFeatures(SequenceI seq, String featureType, int score) { addFeature(seq, featureType, score++); addFeature(seq, featureType, score); } private void addFeature(SequenceI seq, String featureType, int score) { SequenceFeature sf = new SequenceFeature(featureType, "desc", 1, 2, score, "grp"); sf.setValue("AF", score); sf.setValue("CSQ", new HashMap() { { put("PolyPhen", Integer.toString(score)); } }); seq.addSequenceFeature(sf); } /** * Test of the behaviour of the Show / Hide checkbox */ @Test(groups = "Functional") public void testHideShow() { AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE, FileFormat.Fasta); SequenceI seq1 = af.getViewport().getAlignment().getSequenceAt(0); seq1.addSequenceFeature( new SequenceFeature("type1", "", 1, 4, "group1")); FeatureRenderer fr = af.getFeatureRenderer(); fr.setColour("type1", new FeatureColour(Color.red)); af.showSeqFeatures_actionPerformed(null); FeatureSettings dialog = new FeatureSettings(af); assertTrue(fr.getDisplayedFeatureTypes().contains("type1")); /* * check the table has one row, for type1, visible, no filter, red */ assertEquals(dialog.table.getRowCount(), 1); assertEquals(dialog.table.getColumnCount(), 4); assertEquals(dialog.table.getModel().getValueAt(0, TYPE_COLUMN), "type1"); FeatureColourI colour = (FeatureColourI) dialog.table.getModel() .getValueAt(0, COLOUR_COLUMN); assertTrue(colour.isSimpleColour()); assertEquals(colour.getColour(), Color.red); FeatureMatcherSetI filter = (FeatureMatcherSetI) dialog.table.getModel() .getValueAt(0, FILTER_COLUMN); assertTrue(filter.isEmpty()); assertEquals(dialog.table.getModel().getValueAt(0, SHOW_COLUMN), Boolean.TRUE); /* * set feature type to hidden by clicking the checkbox in column 4, * and verify that now no feature types are displayed */ dialog.table.setValueAt(Boolean.FALSE, 0, SHOW_COLUMN); assertTrue(fr.getDisplayedFeatureTypes().isEmpty()); /* * set feature type back to visible by clicking the checkbox in column 4, * and verify that now the feature type is displayed */ dialog.table.setValueAt(Boolean.TRUE, 0, SHOW_COLUMN); assertTrue(fr.getDisplayedFeatureTypes().contains("type1")); } /** * Test Cancel resets any changes made */ @Test(groups = "Functional") public void testCancel() { AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE, FileFormat.Fasta); SequenceI seq1 = af.getViewport().getAlignment().getSequenceAt(0); seq1.addSequenceFeature( new SequenceFeature("type1", "", 1, 4, "group1")); seq1.addSequenceFeature( new SequenceFeature("type2", "", 1, 4, "group2")); /* * set type1: red, filter 'Label Contains metal' * type2: variable colour red:blue, no filter */ FeatureRenderer fr = af.getFeatureRenderer(); fr.setColour("type1", new FeatureColour(Color.red)); fr.setColour("type2", new FeatureColour(Color.red, Color.blue, 0f, 1f)); FeatureMatcherSetI f = new FeatureMatcherSet(); f.and(FeatureMatcher.byLabel(Condition.Contains, "metal")); fr.setFeatureFilter("type1", f); af.showSeqFeatures_actionPerformed(null); FeatureSettings dialog = new FeatureSettings(af); assertTrue(fr.getDisplayedFeatureTypes().contains("type1")); assertTrue(fr.getDisplayedFeatureTypes().contains("type2")); /* * check the table has two rows, for type1 and type2 * note type2 is shown first; the initial ordering is 'random' as driven by * the Set of feature groups for the sequence */ assertEquals(dialog.table.getRowCount(), 2); assertEquals(dialog.table.getColumnCount(), 4); assertEquals(dialog.table.getModel().getValueAt(0, TYPE_COLUMN), "type2"); FeatureColourI colour = (FeatureColourI) dialog.table.getModel() .getValueAt(0, COLOUR_COLUMN); assertFalse(colour.isSimpleColour()); assertEquals(colour.getMinColour(), Color.red); assertEquals(colour.getMaxColour(), Color.blue); FeatureMatcherSetI filter = (FeatureMatcherSetI) dialog.table.getModel() .getValueAt(0, FILTER_COLUMN); assertTrue(filter.isEmpty()); assertEquals(dialog.table.getModel().getValueAt(0, SHOW_COLUMN), Boolean.TRUE); assertEquals(dialog.table.getModel().getValueAt(1, TYPE_COLUMN), "type1"); colour = (FeatureColourI) dialog.table.getModel().getValueAt(1, COLOUR_COLUMN); assertTrue(colour.isSimpleColour()); assertEquals(colour.getColour(), Color.red); filter = (FeatureMatcherSetI) dialog.table.getModel().getValueAt(1, FILTER_COLUMN); assertFalse(filter.isEmpty()); assertEquals(dialog.table.getModel().getValueAt(1, SHOW_COLUMN), Boolean.TRUE); /* * Make some changes: * - set type1 to hidden by clicking the checkbox in column 4 * - change type2 to plain colour green * We can do these by setting values in the table model - * updateFeatureRenderer then updates visibility and colour */ dialog.table.setValueAt(Boolean.FALSE, 1, SHOW_COLUMN); dialog.table.setValueAt(new FeatureColour(Color.green), 0, COLOUR_COLUMN); /* * - remove the filter on type1 * - set a filter on type2 * We have to do this directly on FeatureRendererModel (as done in the * application from FeatureTypeSettings) */ fr.setFeatureFilter("type1", null); fr.setFeatureFilter("type2", FeatureMatcherSet.fromString("Label matches Metal")); /* * verify the changes reached the FeatureRender */ assertFalse(fr.getDisplayedFeatureTypes().contains("type1")); assertNull(fr.getFeatureFilter("type1")); colour = fr.getFeatureColours().get("type2"); assertTrue(colour.isSimpleColour()); assertEquals(colour.getColour(), Color.green); filter = fr.getFeatureFilter("type2"); assertFalse(filter.isEmpty()); assertEquals(filter.toStableString(), "Label Matches Metal"); /* * add a new feature type and 'notify' FeatureRenderer * it should appear in the first row of FeatureSettings table */ seq1.addSequenceFeature( new SequenceFeature("type3", "desc", 4, 5, null)); fr.featuresAdded(); assertEquals(dialog.table.getRowCount(), 3); assertEquals(dialog.table.getModel().getValueAt(0, TYPE_COLUMN), "type3"); assertEquals(dialog.table.getModel().getValueAt(0, SHOW_COLUMN), Boolean.TRUE); assertTrue(fr.getDisplayedFeatureTypes().contains("type3")); /* * cancel the dialog, and verify FeatureRenderer data has been reset */ dialog.cancel(); // type1 has been reset to visible: assertTrue(fr.getDisplayedFeatureTypes().contains("type1")); // type3 has been reset to not visible: assertTrue(fr.getDisplayedFeatureTypes().contains("type3")); // type2 colour has been reset to graduated: colour = fr.getFeatureColours().get("type2"); assertFalse(colour.isSimpleColour()); assertEquals(colour.getMaxColour(), Color.blue); // type2 filter has been reset to none (held as null): assertNull(fr.getFeatureFilter("type2")); // type1 filter has been reset: filter = fr.getFeatureFilter("type1"); assertEquals(filter.toStableString(), "Label Contains metal"); } }