From: Jim Procter Date: Mon, 17 Dec 2018 11:54:21 +0000 (+0000) Subject: Merge branch 'develop' into bug/JAL-2541cutRelocateFeatures X-Git-Tag: Release_2_11_1_0~98^2~2 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=b83eff8c672cede0305da3c76823dab414749dde;hp=d943fcd0dc04fd4974344eddd13902c89fb595b2;p=jalview.git Merge branch 'develop' into bug/JAL-2541cutRelocateFeatures --- diff --git a/.classpath b/.classpath index d704f10..0da91bb 100644 --- a/.classpath +++ b/.classpath @@ -36,7 +36,6 @@ - @@ -48,11 +47,11 @@ - + + - @@ -68,6 +67,8 @@ + + diff --git a/.gitignore b/.gitignore index 2a55560..86637df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .project /dist +/clover /classes /tests /test-reports @@ -12,4 +13,4 @@ TESTNG /jalviewApplet.jar /benchmarking/lib -*.class \ No newline at end of file +*.class diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 8a5e7a7..5908bb2 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,15 +1,15 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=52 diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs index 30e76be..958613b 100644 --- a/.settings/org.eclipse.jdt.ui.prefs +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -40,6 +40,7 @@ sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class= sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=false sp_cleanup.remove_trailing_whitespaces_all=true diff --git a/AUTHORS b/AUTHORS index 1bfc734..2fe3fce 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,15 +7,17 @@ or might otherwise be considered author of Jalview. The people listed below are 'The Jalview Authors', who collectively own the copyright to the Jalview source code and permit it to be released under GPL. -This is the authoritative list. It was correct on 23rd November 2016. +This is the authoritative list: it was correct on 5th September 2018 (or the last commit date!) + If you are releasing a version of Jalview, please make sure any statement of authorship in the GUI reflects the list shown here. In particular, check the resources/authors.props file ! Jim Procter Mungo Carstairs -Tochukwu 'Charles' Ofoegbu +Ben Soares Kira Mourao +Tochukwu 'Charles' Ofoegbu Andrew Waterhouse Jan Engelhardt Lauren Lui diff --git a/README b/README index eaf226b..8172066 100755 --- a/README +++ b/README @@ -21,13 +21,18 @@ For more help, read the file doc/building.html ################## -To run application: +To run application... +[ NOTE: when using the -classpath option with the '*' wildcard, the argument must be quoted to avoid shell expansion of the wildcard, + ALSO, the wildcard MUST be as DIR/* and not DIR/*.jar etc or it will not be interpreted correctly ] -java -Djava.ext.dirs=JALVIEW_HOME/lib -cp JALVIEW_HOME/jalview.jar jalview.bin.Jalview +on Windows use: + java -classpath "JALVIEW_HOME/lib/*;JALVIEW_HOME/jalview.jar" jalview.bin.Jalview +and on MacOS or Linux: + java -classpath "JALVIEW_HOME/lib/*:JALVIEW_HOME/jalview.jar" jalview.bin.Jalview Replace JALVIEW_HOME with the full path to Jalview Installation Directory. If building from source: -java -Djava.ext.dirs=JALVIEW_BUILD/dist -cp JALVIEW_BUILD/dist/jalview.jar jalview.bin.Jalview + java -classpath "JALVIEW_BUILD/dist/*" jalview.bin.Jalview ################## diff --git a/RELEASE b/RELEASE index f1faf34..1960368 100644 --- a/RELEASE +++ b/RELEASE @@ -1,2 +1,2 @@ -jalview.release=releases/Release_2_10_3_Branch -jalview.version=2.10.3 +jalview.release=releases/Release_2_11_Branch +jalview.version=2.11.0 diff --git a/THIRDPARTYLIBS b/THIRDPARTYLIBS index e0be904..afa99d2 100644 --- a/THIRDPARTYLIBS +++ b/THIRDPARTYLIBS @@ -9,6 +9,7 @@ ext.edu.ucsf.rbvi.strucviz2 includes sources originally developed by Scooter Mor jalview.ext.android includes code taken from the Android Open Source Project (https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util). The Apache 2.0 Licence (http://www.apache.org/licenses/LICENSE-2.0) is acknowledged in the source code. + org.stackoverflowusers.file.WindowsShortcuts was downloaded from http://github.com/codebling/WindowsShortcuts via https://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java Licensing information for each library is given below: @@ -29,7 +30,6 @@ httpclient-4.0.3.jar httpcore-4.0.1.jar httpmime-4.0.3.jar jaxrpc.jar -jdas-1.0.4.jar : Apache License - built from http://code.google.com/p/jdas/ (29th Feb 2012) jhall.jar jswingreader-0.3.jar : Apache license - built from http://jswingreader.sourceforge.net/ svn/trunk v12 log4j-1.2.8.jar @@ -38,8 +38,8 @@ miglayout-4.0-swing.jar BSD http://www.migcalendar.com/miglayout/versions/4.0/li min-jaba-client.jar regex.jar saaj.jar -spring-core-3.0.5.RELEASE.jar : Apache License: jdas runtime dependencies retrieved via maven -spring-web-3.0.5.RELEASE.jar : Apache License: jdas runtime dependencies retrieved via maven +spring-core-3.0.5.RELEASE.jar : Apache License: jdas runtime dependencies retrieved via maven - TODO: JAL-3035 remove if no longer needed ? +spring-web-3.0.5.RELEASE.jar : Apache License: jdas runtime dependencies retrieved via maven - TODO: JAL-3035 remove if no longer needed ? vamsas-client.jar wsdl4j.jar xercesImpl.jar @@ -49,13 +49,21 @@ jfreesvg-2.1.jar : GPL v3 licensed library from the JFree suite: http://www.jfre quaqua: v.8.0 (latest stable) by Randel S Hofer. LGPL and BSD Modified license: downloaded from http://www.randelshofer.ch/quaqua/ -lib/htsjdk-1.120-SNAPSHOT.jar: (currently not required for 2.10) built from maven master at https://github.com/samtools/htsjdk MIT License to Broad Institute +vaqua5-patch: This is a patched version of VAqua v5 (latest stable) by Alan Snyder et al. GPLv3 with Classpath exception, also includes contributions from Quaqua: http://violetlib.org/vaqua/overview.html - see doc/patching-vaqua.txt for patch details, and http://issues.jalview.org/browse/JAL-2988 for details of the bug that the patch addresses. + +lib/htsjdk-2.12.jar: built from maven master at https://github.com/samtools/htsjdk MIT License to Broad Institute lib/biojava-core-4.1.0.jar LGPLv2.1 - latest license at https://github.com/biojava/biojava/blob/master/LICENSE lib/biojava-ontology-4.1.0.jar LGPLv2.1 - latest license at https://github.com/biojava/biojava/blob/master/LICENSE +Libraries for Test Suite + +utils/classgraph-4.1.6.jar: BSD License - allows recovery of classpath for programmatic construction of a Java command line to launch Jalview + version 4.1.6 downloaded from https://mvnrepository.com/artifact/io.github.classgraph/classgraph/4.1.6 + + Additional dependencies examples/javascript/deployJava.js : http://java.com/js/deployJava.js diff --git a/benchmarking/README b/benchmarking/README index ccf53c1..d1ec146 100644 --- a/benchmarking/README +++ b/benchmarking/README @@ -27,6 +27,9 @@ to install the jalview.jar file in the local maven repository. The pom.xml in th To get JSON output instead use: java -jar target/benchmarks.jar -rf json + To run a specific benchmark file use: + java -jar target/benchmarks.jar + JSON output can be viewed quickly by drag-dropping on http://jmh.morethan.io/ To get help use the standard -h option: @@ -38,3 +41,4 @@ to install the jalview.jar file in the local maven repository. The pom.xml in th 6. If you make changes to the Jalview code everything will need to be refreshed, by performing steps 3-5 again. + diff --git a/benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java b/benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java new file mode 100644 index 0000000..477bfad --- /dev/null +++ b/benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java @@ -0,0 +1,140 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ + +package org.jalview; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Param; + +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.HiddenColumns; + +/* + * A class to benchmark hidden columns performance + */ +@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Fork(1) +public class HiddenColsIteratorsBenchmark { + /* + * State with multiple hidden columns and a start position set + */ + @State(Scope.Thread) + public static class HiddenColsAndStartState + { + @Param({"300", "10000", "100000"}) + public int maxcols; + + @Param({"1", "50", "90"}) + public int startpcnt; // position as percentage of maxcols + + @Param({"1","15","100"}) + public int hide; + + HiddenColumns h = new HiddenColumns(); + Random rand = new Random(); + + public int hiddenColumn; + public int visibleColumn; + + @Setup + public void setup() + { + rand.setSeed(1234); + int lastcol = 0; + while (lastcol < maxcols) + { + int count = rand.nextInt(100); + lastcol += count; + h.hideColumns(lastcol, lastcol+hide); + lastcol+=hide; + } + + // make sure column at start is hidden + hiddenColumn = (int)(maxcols * startpcnt/100.0); + h.hideColumns(hiddenColumn, hiddenColumn); + + // and column after start is visible + ColumnSelection sel = new ColumnSelection(); + h.revealHiddenColumns(hiddenColumn+hide, sel); + visibleColumn = hiddenColumn+hide; + + System.out.println("Maxcols: " + maxcols + " HiddenCol: " + hiddenColumn + " Hide: " + hide); + System.out.println("Number of hidden columns: " + h.getSize()); + } + } + + /* Convention: functions in alphabetical order */ + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public int benchStartIterator(HiddenColsAndStartState tstate) + { + int res = 0; + int startx = tstate.visibleColumn; + Iterator it = tstate.h.getStartRegionIterator(startx, + startx+60); + while (it.hasNext()) + { + res = it.next() - startx; + Blackhole.consumeCPU(5); + } + return res; + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public int benchBoundedIterator(HiddenColsAndStartState tstate) + { + int startx = tstate.visibleColumn; + int blockStart = startx; + int blockEnd; + int screenY = 0; + Iterator it = tstate.h.getBoundedIterator(startx, + startx+60); + while (it.hasNext()) + { + int[] region = it.next(); + + blockEnd = Math.min(region[0] - 1, blockStart + 60 - screenY); + + screenY += blockEnd - blockStart + 1; + blockStart = region[1] + 1; + + Blackhole.consumeCPU(5); + } + return blockStart; + } +} diff --git a/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java b/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java index d3c67d7..07b76f9 100644 --- a/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java +++ b/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java @@ -47,128 +47,111 @@ import jalview.datamodel.HiddenColumns; @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Fork(1) public class HiddenColumnsBenchmark -{ - /* - * State with multiple hidden columns and a start position set - */ - @State(Scope.Thread) - public static class HiddenColsAndStartState - { - @Param({ "300", "10000", "100000" }) - public int maxcols; - - @Param({ "1", "50", "90" }) - public int startpcnt; // position as percentage of maxcols - - @Param({ "1", "15", "100" }) - public int hide; - - HiddenColumns h = new HiddenColumns(); - - Random rand = new Random(); - - public int hiddenColumn; - - public int visibleColumn; - - @Setup - public void setup() +{ + /* + * State with multiple hidden columns and a start position set + */ + @State(Scope.Thread) + public static class HiddenColsAndStartState + { + @Param({"100", "1000", "10000", "100000", "1000000"}) + public int maxcols; + + @Param({"1", "50", "90"}) + public int startpcnt; // position as percentage of maxcols + + @Param({"1","15","100"}) + public int hide; + + HiddenColumns h = new HiddenColumns(); + Random rand = new Random(); + + public int hiddenColumn; + public int visibleColumn; + + @Setup + public void setup() + { + rand.setSeed(1234); + int lastcol = 0; + while (lastcol < maxcols) + { + int count = rand.nextInt(100); + lastcol += count; + h.hideColumns(lastcol, lastcol+hide); + lastcol+=hide; + } + + // make sure column at start is hidden + hiddenColumn = (int)(maxcols * startpcnt/100.0); + h.hideColumns(hiddenColumn, hiddenColumn); + + // and column after start is visible + ColumnSelection sel = new ColumnSelection(); + h.revealHiddenColumns(hiddenColumn+hide, sel); + visibleColumn = hiddenColumn+hide; + + System.out.println("Maxcols: " + maxcols + " HiddenCol: " + hiddenColumn + " Hide: " + hide); + System.out.println("Number of hidden columns: " + h.getSize()); + } + } + + /* Convention: functions in alphabetical order */ + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public int benchAdjustForHiddenColumns(HiddenColsAndStartState tstate) + { + return tstate.h.visibleToAbsoluteColumn(tstate.visibleColumn); + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public int benchFindColumnPosition(HiddenColsAndStartState tstate) + { + return tstate.h.absoluteToVisibleColumn(tstate.visibleColumn); + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public int benchGetSize(HiddenColsAndStartState tstate) { - rand.setSeed(1234); - int lastcol = 0; - while (lastcol < maxcols) - { - int count = rand.nextInt(100); - lastcol += count; - h.hideColumns(lastcol, lastcol + hide); - lastcol += hide; - } - - // make sure column at start is hidden - hiddenColumn = (int) (maxcols * startpcnt / 100.0); - h.hideColumns(hiddenColumn, hiddenColumn); - - // and column after start is visible - ColumnSelection sel = new ColumnSelection(); - h.revealHiddenColumns(hiddenColumn + hide, sel); - visibleColumn = hiddenColumn + hide; - - System.out.println("Maxcols: " + maxcols + " HiddenCol: " - + hiddenColumn + " Hide: " + hide); - System.out.println("Number of hidden columns: " + h.getSize()); + return tstate.h.getSize(); } - } - - /* Convention: functions in alphabetical order */ - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public int benchAdjustForHiddenColumns(HiddenColsAndStartState tstate) - { - return tstate.h.adjustForHiddenColumns(tstate.visibleColumn); - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public int benchFindColumnPosition(HiddenColsAndStartState tstate) - { - return tstate.h.findColumnPosition(tstate.visibleColumn); - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public List benchFindHiddenRegionPositions( - HiddenColsAndStartState tstate) - { - return tstate.h.findHiddenRegionPositions(); - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public ArrayList benchGetHiddenColumnsCopy( - HiddenColsAndStartState tstate) - { - return tstate.h.getHiddenColumnsCopy(); - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public int benchGetSize(HiddenColsAndStartState tstate) - { - return tstate.h.getSize(); - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public HiddenColumns benchHideCols(HiddenColsAndStartState tstate) - { - tstate.h.hideColumns(tstate.visibleColumn, tstate.visibleColumn + 2000); - return tstate.h; - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public boolean benchIsVisible(HiddenColsAndStartState tstate) - { - return tstate.h.isVisible(tstate.hiddenColumn); - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public HiddenColumns benchReveal(HiddenColsAndStartState tstate) - { - ColumnSelection sel = new ColumnSelection(); - tstate.h.revealHiddenColumns(tstate.hiddenColumn, sel); - return tstate.h; - } - - @Benchmark - @BenchmarkMode({ Mode.Throughput }) - public HiddenColumns benchRevealAll(HiddenColsAndStartState tstate) - { - ColumnSelection sel = new ColumnSelection(); - tstate.h.revealAllHiddenColumns(sel); - return tstate.h; - } + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public HiddenColumns benchHideCols(HiddenColsAndStartState tstate) + { + tstate.h.hideColumns(tstate.visibleColumn, + tstate.visibleColumn+2000); + return tstate.h; + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public boolean benchIsVisible(HiddenColsAndStartState tstate) + { + return tstate.h.isVisible(tstate.hiddenColumn); + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public HiddenColumns benchReveal(HiddenColsAndStartState tstate) + { + ColumnSelection sel = new ColumnSelection(); + tstate.h.revealHiddenColumns(tstate.hiddenColumn, sel); + return tstate.h; + } + + @Benchmark + @BenchmarkMode({Mode.Throughput}) + public HiddenColumns benchRevealAll(HiddenColsAndStartState tstate) + { + ColumnSelection sel = new ColumnSelection(); + tstate.h.revealAllHiddenColumns(sel); + return tstate.h; + } + + } \ No newline at end of file diff --git a/benchmarking/src/main/java/org/jalview/SeqWidthBenchmark.java b/benchmarking/src/main/java/org/jalview/SeqWidthBenchmark.java new file mode 100644 index 0000000..a92d4f0 --- /dev/null +++ b/benchmarking/src/main/java/org/jalview/SeqWidthBenchmark.java @@ -0,0 +1,96 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ + +package org.jalview; + +import org.jalview.HiddenColumnsBenchmark.HiddenColsAndStartState; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Param; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; + +/* + * A class to benchmark hidden columns performance + */ +@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Fork(1) +public class SeqWidthBenchmark { + + /* + * State with multiple hidden columns and a start position set + */ + @State(Scope.Thread) + public static class AlignmentState + { + @Param({"100", "1000", "10000", "100000"}) + public int numSeqs; + + Random rand = new Random(); + + AlignmentI al; + + @Setup + public void setup() + { + rand.setSeed(1234); + + SequenceI[] seqs = new Sequence[numSeqs]; + for (int i = 0; i < numSeqs; i++) + { + int count = rand.nextInt(10000); + StringBuilder aas = new StringBuilder(); + for (int j=0; j. * The Jalview Authors are detailed in the 'AUTHORS' file. --> - + + + + @@ -36,8 +41,9 @@ - + + @@ -96,6 +102,10 @@ + + + + @@ -106,8 +116,8 @@ - - + + @@ -124,7 +134,8 @@ - + + @@ -176,17 +187,14 @@ - - - + - + @@ -296,13 +304,23 @@ verbose="2"> + + + + + + + + + + @@ -338,10 +356,15 @@ - + + + + + + @@ -351,7 +374,7 @@ - + @@ -378,7 +401,7 @@ --> - + @@ -386,7 +409,7 @@ - + @@ -425,19 +448,20 @@ - + - + - + + @@ -451,10 +475,9 @@ - - j2se version="1.7+" - - + + j2se version="1.8+" + @@ -526,6 +549,9 @@ + + Ignoring request to build jalview distribution with clover-instrumented classes + @@ -581,7 +607,6 @@ - @@ -599,7 +624,6 @@ - @@ -624,7 +648,7 @@ - + @@ -917,13 +941,13 @@ - + - + diff --git a/doc/patching-vaqua.txt b/doc/patching-vaqua.txt new file mode 100644 index 0000000..65c9974 --- /dev/null +++ b/doc/patching-vaqua.txt @@ -0,0 +1,27 @@ +VAqua5-patched.jar - how the patch was created + +1. Download VAqua5 source from https://violetlib.org/release/vaqua/5/VAqua5Source.zip +2. Unzip to a directory and apply this patch + +diff --git a/src/org/violetlib/aqua/fc/AquaFileChooserUI.java b/src/org/violetlib/aqua/fc/AquaFileChooserUI.java +index 833366d..61f66e5 100644 +--- a/src/org/violetlib/aqua/fc/AquaFileChooserUI.java ++++ b/src/org/violetlib/aqua/fc/AquaFileChooserUI.java +@@ -1171,7 +1171,8 @@ public class AquaFileChooserUI extends BasicFileChooserUI { + goToFolderCancelButtonText = getString("FileChooser.goToFolderCancelButtonText", l, "Cancel"); + goToFolderAcceptButtonText = getString("FileChooser.goToFolderAcceptButtonText", l, "Accept"); + goToFolderErrorText = getString("FileChooser.goToFolderErrorText", l, "The folder can\u2019t be found."); +- defaultInitialSaveFileName = getString("FileChooser.defaultSaveFileName", l, "Untitled"); ++ // Don't set an initial filename for saving (or loading) ! ++ // defaultInitialSaveFileName = getString("FileChooser.defaultSaveFileName", l, "Untitled"); + } + + /** + +3. Ensure XCode is installed, along with command line tools and the OSX developer packs + - you should have /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk + +4. Download the VAqua rendering library from violetlib.org and save to the VAqua source's lib folder as lib/VAquaRendering.jar + +5. change to the release directory and execute 'ant' - a few warnings are generated but providing a final jar is created, all is good! + diff --git a/examples/exampleFeatures.txt b/examples/exampleFeatures.txt index 9e65534..99af214 100755 --- a/examples/exampleFeatures.txt +++ b/examples/exampleFeatures.txt @@ -26,6 +26,11 @@ BETA-TURN-IIL 8b5b50 ST-MOTIF ac25a1 kdHydrophobicity ccffcc|333300|-3.9|4.5|above|-2.0 +STARTFILTERS +GAMMA-TURN-INVERSE Label Contains PDB +kdHydrophobicity (Score LT 1.5) OR (Score GE 2.8) +ENDFILTERS + STARTGROUP uniprot Pfam family FER_CAPAA -1 0 0 Pfam Iron-sulfur (2Fe-2S) FER_CAPAA -1 39 39 METAL diff --git a/examples/rna_ss_test.stk b/examples/rna_ss_test.stk new file mode 100644 index 0000000..429612e --- /dev/null +++ b/examples/rna_ss_test.stk @@ -0,0 +1,6 @@ +# STOCKHOLM 1.0 +#=GF ID RNA.SS.TEST +#=GF TP RNA; +Test.sequence GUACAAAAAAAAAA +#=GC SS_cons <(EHBheb(E)e)> +// diff --git a/examples/testdata/4IM2_missing.pdb b/examples/testdata/4IM2_missing.pdb new file mode 100644 index 0000000..35f94b2 --- /dev/null +++ b/examples/testdata/4IM2_missing.pdb @@ -0,0 +1,525 @@ +HEADER TRANSFERASE/TRANSFERASE INHIBITOR 01-JAN-13 4IM2 +TITLE STRUCTURE OF TANK-BINDING KINASE 1 (Truncated test file) +ATOM 3510 N LEU A 468 76.337 90.332 -7.628 1.00168.53 N +ANISOU 3510 N LEU A 468 19760 15191 29082 5189 1248 -1702 N +ATOM 3511 CA LEU A 468 75.576 90.788 -6.476 1.00181.60 C +ANISOU 3511 CA LEU A 468 21073 16927 30998 5158 1421 -2051 C +ATOM 3512 C LEU A 468 76.285 91.971 -5.836 1.00196.57 C +ANISOU 3512 C LEU A 468 23029 18636 33021 5053 1667 -2229 C +ATOM 3513 O LEU A 468 75.727 93.067 -5.733 1.00197.97 O +ANISOU 3513 O LEU A 468 23115 18581 33523 5166 1662 -2304 O +ATOM 3514 CB LEU A 468 75.409 89.671 -5.449 1.00173.72 C +ANISOU 3514 CB LEU A 468 19829 16340 29836 4981 1593 -2304 C +ATOM 3515 CG LEU A 468 74.099 88.882 -5.420 1.00170.58 C +ANISOU 3515 CG LEU A 468 19142 16142 29529 5074 1449 -2345 C +ATOM 3516 CD1 LEU A 468 74.023 87.940 -6.581 1.00167.98 C +ANISOU 3516 CD1 LEU A 468 18951 15858 29015 5180 1156 -2025 C +ATOM 3517 CD2 LEU A 468 73.988 88.096 -4.145 1.00165.16 C +ANISOU 3517 CD2 LEU A 468 18217 15818 28720 4865 1706 -2656 C +ATOM 3518 N ASP A 469 77.529 91.753 -5.429 1.00208.80 N +ANISOU 3518 N ASP A 469 24731 20282 34322 4836 1875 -2296 N +ATOM 3519 CA ASP A 469 78.267 92.785 -4.720 1.00223.11 C +ANISOU 3519 CA ASP A 469 26577 21960 36233 4701 2123 -2500 C +ATOM 3520 C ASP A 469 78.653 93.973 -5.599 1.00231.96 C +ANISOU 3520 C ASP A 469 27954 22654 37525 4817 2037 -2283 C +ATOM 3521 O ASP A 469 78.718 95.100 -5.118 1.00235.71 O +ANISOU 3521 O ASP A 469 28377 22940 38242 4798 2175 -2449 O +ATOM 3522 CB ASP A 469 79.506 92.197 -4.057 1.00224.50 C +ANISOU 3522 CB ASP A 469 26841 22370 36089 4433 2350 -2633 C +ATOM 3523 CG ASP A 469 79.748 92.767 -2.679 1.00229.51 C +ANISOU 3523 CG ASP A 469 27303 23107 36794 4254 2637 -3018 C +ATOM 3524 OD1 ASP A 469 79.958 93.992 -2.564 1.00232.36 O +ANISOU 3524 OD1 ASP A 469 27695 23203 37387 4262 2718 -3093 O +ATOM 3525 OD2 ASP A 469 79.751 91.979 -1.709 1.00229.48 O +ANISOU 3525 OD2 ASP A 469 27145 23450 36598 4098 2779 -3242 O +ATOM 3526 N PHE A 470 78.904 93.735 -6.882 1.00233.88 N +ANISOU 3526 N PHE A 470 28481 22742 37641 4931 1813 -1912 N +ATOM 3527 CA PHE A 470 79.292 94.831 -7.766 1.00237.73 C +ANISOU 3527 CA PHE A 470 29252 22819 38256 5033 1733 -1677 C +ATOM 3528 C PHE A 470 78.103 95.470 -8.467 1.00232.15 C +ANISOU 3528 C PHE A 470 28512 21868 37828 5322 1470 -1512 C +ATOM 3529 O PHE A 470 78.021 96.691 -8.532 1.00230.69 O +ANISOU 3529 O PHE A 470 28371 21372 37909 5402 1493 -1517 O +ATOM 3530 CB PHE A 470 80.344 94.379 -8.784 1.00242.99 C +ANISOU 3530 CB PHE A 470 30297 23410 38616 4976 1661 -1357 C +ATOM 3531 CG PHE A 470 80.663 95.388 -9.839 1.00251.53 C +ANISOU 3531 CG PHE A 470 31709 24074 39788 5089 1550 -1064 C +ATOM 3532 CD1 PHE A 470 81.658 96.315 -9.620 1.00252.90 C +ANISOU 3532 CD1 PHE A 470 32021 24036 40032 4942 1771 -1133 C +ATOM 3533 CD2 PHE A 470 80.005 95.389 -11.059 1.00255.02 C +ANISOU 3533 CD2 PHE A 470 32335 24339 40221 5330 1225 -715 C +ATOM 3534 CE1 PHE A 470 81.981 97.242 -10.575 1.00254.75 C +ANISOU 3534 CE1 PHE A 470 32569 23879 40344 5028 1688 -862 C +ATOM 3535 CE2 PHE A 470 80.321 96.320 -12.027 1.00256.76 C +ANISOU 3535 CE2 PHE A 470 32894 24173 40491 5427 1126 -433 C +ATOM 3536 CZ PHE A 470 81.313 97.248 -11.784 1.00256.57 C +ANISOU 3536 CZ PHE A 470 33006 23929 40550 5272 1365 -504 C +ATOM 3537 N CYS A 471 77.190 94.665 -8.997 1.00176.88 N +ANISOU 3537 N CYS A 471 20955 21812 24441 2225 -3821 -3711 N +ATOM 3538 CA CYS A 471 76.124 95.233 -9.815 1.00176.41 C +ANISOU 3538 CA CYS A 471 20820 21906 24301 2124 -3576 -3614 C +ATOM 3539 C CYS A 471 74.868 95.631 -9.050 1.00176.82 C +ANISOU 3539 C CYS A 471 21026 22206 23952 2247 -3588 -3734 C +ATOM 3540 O CYS A 471 74.632 96.811 -8.854 1.00178.66 O +ANISOU 3540 O CYS A 471 21175 22393 24314 2353 -3749 -3873 O +ATOM 3541 CB CYS A 471 75.805 94.346 -11.019 1.00173.95 C +ANISOU 3541 CB CYS A 471 20521 21694 23877 1925 -3217 -3359 C +ATOM 3542 SG CYS A 471 76.954 94.571 -12.398 1.00273.04 S +ANISOU 3542 SG CYS A 471 32850 33934 36957 1863 -3075 -3146 S +ATOM 3543 N ILE A 472 74.068 94.670 -8.603 1.00173.50 N +ANISOU 3543 N ILE A 472 20813 22023 23085 2253 -3402 -3662 N +ATOM 3544 CA ILE A 472 72.771 95.033 -8.022 1.00171.09 C +ANISOU 3544 CA ILE A 472 20616 21936 22453 2374 -3307 -3694 C +ATOM 3545 C ILE A 472 72.832 96.022 -6.874 1.00177.82 C +ANISOU 3545 C ILE A 472 21591 22750 23222 2698 -3611 -3931 C +ATOM 3546 O ILE A 472 71.992 96.914 -6.776 1.00182.92 O +ANISOU 3546 O ILE A 472 22220 23491 23792 2778 -3598 -3985 O +ATOM 3547 CB ILE A 472 71.979 93.843 -7.518 1.00159.41 C +ANISOU 3547 CB ILE A 472 19318 20653 20599 2393 -3038 -3546 C +ATOM 3548 CG1 ILE A 472 70.708 93.667 -8.341 1.00147.27 C +ANISOU 3548 CG1 ILE A 472 17653 19253 19051 2196 -2745 -3384 C +ATOM 3549 CG2 ILE A 472 71.526 94.090 -6.105 1.00155.95 C +ANISOU 3549 CG2 ILE A 472 19129 20312 19812 2752 -3085 -3637 C +ATOM 3550 CD1 ILE A 472 70.927 92.915 -9.606 1.00135.92 C +ANISOU 3550 CD1 ILE A 472 16097 17752 17794 1929 -2643 -3254 C +ATOM 3551 N ARG A 473 73.812 95.865 -5.996 1.00180.33 N +ANISOU 3551 N ARG A 473 22055 22917 23544 2920 -3920 -4092 N +ATOM 3552 CA ARG A 473 73.860 96.762 -4.877 1.00189.65 C +ANISOU 3552 CA ARG A 473 23415 24034 24610 3307 -4283 -4357 C +ATOM 3553 C ARG A 473 74.328 98.021 -5.558 1.00185.98 C +ANISOU 3553 C ARG A 473 22628 23344 24692 3195 -4530 -4477 C +ATOM 3554 O ARG A 473 73.519 98.885 -5.866 1.00181.76 O +ANISOU 3554 O ARG A 473 22002 22904 24153 3165 -4446 -4474 O +ATOM 3555 CB ARG A 473 74.803 96.287 -3.768 1.00205.24 C +ANISOU 3555 CB ARG A 473 25661 25862 26460 3635 -4629 -4545 C +ATOM 3556 CG ARG A 473 74.468 96.921 -2.433 1.00220.50 C +ANISOU 3556 CG ARG A 473 27955 27814 28011 4171 -4923 -4787 C +ATOM 3557 CD ARG A 473 75.337 96.433 -1.295 1.00232.58 C +ANISOU 3557 CD ARG A 473 29837 29192 29341 4591 -5300 -5003 C +ATOM 3558 NE ARG A 473 76.763 96.602 -1.549 1.00237.46 N +ANISOU 3558 NE ARG A 473 30220 29440 30564 4489 -5773 -5205 N +ATOM 3559 CZ ARG A 473 77.716 95.991 -0.861 1.00240.32 C +ANISOU 3559 CZ ARG A 473 30786 29623 30903 4725 -6098 -5371 C +ATOM 3560 NH1 ARG A 473 77.388 95.172 0.115 1.00241.12 N +ANISOU 3560 NH1 ARG A 473 31370 29900 30344 5100 -5978 -5355 N +ATOM 3561 NH2 ARG A 473 78.988 96.195 -1.159 1.00241.54 N +ANISOU 3561 NH2 ARG A 473 30651 29398 31724 4605 -6518 -5533 N +ATOM 3562 N ASN A 474 75.608 98.073 -5.901 1.00188.74 N +ANISOU 3562 N ASN A 474 22768 23381 25563 3107 -4772 -4534 N +ATOM 3563 CA ASN A 474 76.168 99.264 -6.526 1.00188.61 C +ANISOU 3563 CA ASN A 474 22395 23075 26194 3026 -4977 -4605 C +ATOM 3564 C ASN A 474 75.420 99.892 -7.729 1.00173.98 C +ANISOU 3564 C ASN A 474 20312 21326 24467 2781 -4623 -4414 C +ATOM 3565 O ASN A 474 75.482 101.107 -7.891 1.00176.08 O +ANISOU 3565 O ASN A 474 20374 21426 25104 2827 -4803 -4518 O +ATOM 3566 CB ASN A 474 77.650 99.066 -6.857 1.00199.45 C +ANISOU 3566 CB ASN A 474 23514 24060 28207 2939 -5170 -4595 C +ATOM 3567 CG ASN A 474 78.504 98.809 -5.625 1.00210.23 C +ANISOU 3567 CG ASN A 474 25062 25219 29597 3243 -5689 -4883 C +ATOM 3568 OD1 ASN A 474 78.174 99.240 -4.527 1.00215.26 O +ANISOU 3568 OD1 ASN A 474 25969 25891 29928 3596 -6043 -5160 O +ATOM 3569 ND2 ASN A 474 79.613 98.103 -5.810 1.00212.07 N +ANISOU 3569 ND2 ASN A 474 25176 25222 30179 3148 -5748 -4822 N +ATOM 3570 N ILE A 475 74.722 99.114 -8.562 1.00161.86 N +ANISOU 3570 N ILE A 475 18813 20033 22652 2556 -4165 -4158 N +ATOM 3571 CA ILE A 475 74.022 99.727 -9.709 1.00154.84 C +ANISOU 3571 CA ILE A 475 17755 19216 21861 2390 -3885 -4015 C +ATOM 3572 C ILE A 475 72.785 100.520 -9.312 1.00159.27 C +ANISOU 3572 C ILE A 475 18401 19995 22121 2490 -3891 -4125 C +ATOM 3573 O ILE A 475 72.662 101.687 -9.663 1.00162.60 O +ANISOU 3573 O ILE A 475 18651 20327 22804 2505 -3962 -4186 O +ATOM 3574 CB ILE A 475 73.600 98.727 -10.818 1.00214.93 C +ANISOU 3574 CB ILE A 475 25397 26975 29293 2173 -3475 -3753 C +ATOM 3575 CG1 ILE A 475 74.808 98.227 -11.613 1.00218.74 C +ANISOU 3575 CG1 ILE A 475 25753 27211 30145 2083 -3390 -3585 C +ATOM 3576 CG2 ILE A 475 72.618 99.379 -11.789 1.00210.37 C +ANISOU 3576 CG2 ILE A 475 24743 26509 28681 2097 -3256 -3677 C +ATOM 3577 CD1 ILE A 475 74.446 97.201 -12.664 1.00218.15 C +ANISOU 3577 CD1 ILE A 475 25777 27260 29850 1946 -3056 -3359 C +ATOM 3578 N GLU A 476 71.856 99.893 -8.600 1.00156.45 N +ANISOU 3578 N GLU A 476 18293 19907 21245 2571 -3778 -4120 N +ATOM 3579 CA GLU A 476 70.623 100.594 -8.252 1.00142.72 C +ANISOU 3579 CA GLU A 476 16627 18370 19231 2677 -3720 -4176 C +ATOM 3580 C GLU A 476 70.845 101.560 -7.094 1.00144.41 C +ANISOU 3580 C GLU A 476 16959 18500 19412 3012 -4114 -4440 C +ATOM 3581 O GLU A 476 70.012 102.417 -6.811 1.00142.58 O +ANISOU 3581 O GLU A 476 16768 18380 19024 3141 -4136 -4517 O +ATOM 3582 CB GLU A 476 69.486 99.616 -7.958 1.00128.68 C +ANISOU 3582 CB GLU A 476 15020 16860 17012 2665 -3395 -4018 C +ATOM 3583 CG GLU A 476 69.562 98.905 -6.628 1.00128.73 C +ANISOU 3583 CG GLU A 476 15321 16930 16662 2941 -3433 -4041 C +ATOM 3584 CD GLU A 476 68.465 97.866 -6.484 1.00133.35 C +ANISOU 3584 CD GLU A 476 15992 17719 16955 2900 -3026 -3806 C +ATOM 3585 OE1 GLU A 476 68.285 97.061 -7.422 1.00132.30 O +ANISOU 3585 OE1 GLU A 476 15710 17598 16962 2603 -2816 -3643 O +ATOM 3586 OE2 GLU A 476 67.768 97.860 -5.448 1.00136.37 O +ANISOU 3586 OE2 GLU A 476 16588 18223 17003 3196 -2912 -3773 O +ATOM 3587 N LYS A 477 71.990 101.424 -6.439 1.00148.46 N +ANISOU 3587 N LYS A 477 17534 18788 20087 3179 -4467 -4596 N +ATOM 3588 CA LYS A 477 72.399 102.365 -5.405 1.00154.25 C +ANISOU 3588 CA LYS A 477 18378 19348 20882 3544 -4976 -4907 C +ATOM 3589 C LYS A 477 72.634 103.732 -5.994 1.00159.76 C +ANISOU 3589 C LYS A 477 18754 19833 22117 3467 -5183 -5014 C +ATOM 3590 O LYS A 477 72.568 104.742 -5.292 1.00158.01 O +ANISOU 3590 O LYS A 477 18598 19516 21921 3749 -5568 -5266 O +ATOM 3591 CB LYS A 477 73.667 101.883 -4.706 1.00154.03 C +ANISOU 3591 CB LYS A 477 18446 19057 21022 3730 -5372 -5076 C +ATOM 3592 CG LYS A 477 73.367 100.787 -3.750 1.00149.76 C +ANISOU 3592 CG LYS A 477 18324 18717 19861 3975 -5258 -5042 C +ATOM 3593 CD LYS A 477 74.437 100.554 -2.740 1.00143.39 C +ANISOU 3593 CD LYS A 477 17737 17664 19082 4327 -5762 -5306 C +ATOM 3594 CE LYS A 477 73.739 100.087 -1.488 1.00145.72 C +ANISOU 3594 CE LYS A 477 18569 18186 18613 4805 -5697 -5337 C +ATOM 3595 NZ LYS A 477 74.661 99.556 -0.469 1.00149.42 N +ANISOU 3595 NZ LYS A 477 19366 18470 18936 5206 -6108 -5561 N +ATOM 3596 N THR A 478 72.908 103.757 -7.293 1.00166.45 N +ANISOU 3596 N THR A 478 19271 20589 23384 3124 -4918 -4810 N +ATOM 3597 CA THR A 478 73.186 105.012 -7.968 1.00170.99 C +ANISOU 3597 CA THR A 478 19509 20929 24531 3052 -5023 -4844 C +ATOM 3598 C THR A 478 71.905 105.733 -8.391 1.00172.33 C +ANISOU 3598 C THR A 478 19686 21358 24435 2999 -4780 -4796 C +ATOM 3599 O THR A 478 71.021 105.170 -9.046 1.00163.10 O +ANISOU 3599 O THR A 478 18581 20461 22928 2823 -4356 -4594 O +ATOM 3600 CB THR A 478 74.217 104.856 -9.128 1.00146.93 C +ANISOU 3600 CB THR A 478 16115 17589 22125 2815 -4843 -4626 C +ATOM 3601 OG1 THR A 478 74.873 106.106 -9.369 1.00147.15 O +ANISOU 3601 OG1 THR A 478 15790 17239 22881 2857 -5085 -4705 O +ATOM 3602 CG2 THR A 478 73.562 104.391 -10.425 1.00144.32 C +ANISOU 3602 CG2 THR A 478 15764 17473 21597 2570 -4282 -4318 C +ATOM 3603 N VAL A 479 71.809 106.979 -7.944 1.00177.73 N +ANISOU 3603 N VAL A 479 20309 21929 25291 3181 -5110 -5014 N +ATOM 3604 CA VAL A 479 70.674 107.838 -8.218 1.00175.65 C +ANISOU 3604 CA VAL A 479 20044 21875 24819 3171 -4959 -5013 C +ATOM 3605 C VAL A 479 71.176 109.077 -8.939 1.00177.67 C +ANISOU 3605 C VAL A 479 19928 21830 25749 3101 -5069 -5035 C +ATOM 3606 O VAL A 479 72.353 109.417 -8.840 1.00179.02 O +ANISOU 3606 O VAL A 479 19865 21600 26555 3150 -5387 -5121 O +ATOM 3607 CB VAL A 479 69.983 108.266 -6.909 1.00168.37 C +ANISOU 3607 CB VAL A 479 19442 21113 23418 3524 -5243 -5249 C +ATOM 3608 CG1 VAL A 479 69.008 109.403 -7.169 1.00162.23 C +ANISOU 3608 CG1 VAL A 479 18603 20470 22565 3531 -5176 -5285 C +ATOM 3609 CG2 VAL A 479 69.288 107.080 -6.256 1.00163.89 C +ANISOU 3609 CG2 VAL A 479 19236 20856 22180 3624 -4991 -5142 C +ATOM 3610 N MET A 486 57.931 103.433 -6.150 1.00 92.00 N +ANISOU 3610 N MET A 486 10154 13425 11375 3371 -1523 -3283 N +ATOM 3611 CA MET A 486 56.824 104.104 -5.482 1.00110.61 C +ANISOU 3611 CA MET A 486 12558 15882 13585 3644 -1287 -3161 C +ATOM 3612 C MET A 486 56.182 105.150 -6.389 1.00111.38 C +ANISOU 3612 C MET A 486 12428 16016 13877 3424 -1416 -3308 C +ATOM 3613 O MET A 486 56.000 106.305 -6.002 1.00120.35 O +ANISOU 3613 O MET A 486 13691 17243 14794 3639 -1526 -3430 O +ATOM 3614 CB MET A 486 57.288 104.743 -4.170 1.00120.60 C +ANISOU 3614 CB MET A 486 14253 17221 14347 4174 -1395 -3243 C +ATOM 3615 CG MET A 486 58.405 105.768 -4.316 1.00126.87 C +ANISOU 3615 CG MET A 486 15171 17987 15047 4196 -1944 -3629 C +ATOM 3616 SD MET A 486 58.150 107.169 -3.210 1.00146.78 S +ANISOU 3616 SD MET A 486 18044 20597 17128 4757 -2124 -3782 S +ATOM 3617 CE MET A 486 58.003 106.326 -1.640 1.00102.35 C +ANISOU 3617 CE MET A 486 12883 15002 11003 5391 -1797 -3524 C +ATOM 3618 N GLY A 496 57.154 99.412 -2.065 1.00157.62 N +ANISOU 3618 N GLY A 496 19150 21656 19083 4616 82 -2024 N +ATOM 3619 CA GLY A 496 57.031 99.217 -3.498 1.00158.07 C +ANISOU 3619 CA GLY A 496 18749 21636 19677 3991 -116 -2141 C +ATOM 3620 C GLY A 496 57.664 97.914 -3.942 1.00163.71 C +ANISOU 3620 C GLY A 496 19349 22235 20619 3709 -140 -2093 C +ATOM 3621 O GLY A 496 58.040 97.090 -3.111 1.00167.17 O +ANISOU 3621 O GLY A 496 20009 22652 20858 3970 79 -1915 O +ATOM 3622 N GLU A 497 57.778 97.722 -5.254 1.00166.67 N +ANISOU 3622 N GLU A 497 19413 22530 21383 3222 -403 -2250 N +ATOM 3623 CA GLU A 497 58.431 96.534 -5.792 1.00170.73 C +ANISOU 3623 CA GLU A 497 19840 22933 22098 2955 -485 -2239 C +ATOM 3624 C GLU A 497 59.931 96.621 -5.556 1.00172.64 C +ANISOU 3624 C GLU A 497 20403 23228 21965 3043 -809 -2472 C +ATOM 3625 O GLU A 497 60.589 95.609 -5.311 1.00173.01 O +ANISOU 3625 O GLU A 497 20547 23223 21965 3047 -764 -2395 O +ATOM 3626 CB GLU A 497 58.173 96.394 -7.290 1.00169.62 C +ANISOU 3626 CB GLU A 497 19361 22681 22408 2505 -727 -2374 C +ATOM 3627 CG GLU A 497 56.882 97.012 -7.777 1.00173.11 C +ANISOU 3627 CG GLU A 497 19518 23086 23171 2413 -663 -2350 C +ATOM 3628 CD GLU A 497 56.703 96.840 -9.270 1.00173.61 C +ANISOU 3628 CD GLU A 497 19329 23013 23621 2054 -974 -2531 C +ATOM 3629 OE1 GLU A 497 57.597 96.241 -9.908 1.00170.78 O +ANISOU 3629 OE1 GLU A 497 19040 22600 23250 1901 -1202 -2647 O +ATOM 3630 OE2 GLU A 497 55.672 97.300 -9.805 1.00174.73 O +ANISOU 3630 OE2 GLU A 497 19233 23093 24064 1967 -999 -2559 O +ATOM 3631 N ILE A 498 60.466 97.836 -5.652 1.00171.44 N +ANISOU 3631 N ILE A 498 20381 23149 21608 3107 -1147 -2757 N +ATOM 3632 CA ILE A 498 61.883 98.078 -5.403 1.00167.93 C +ANISOU 3632 CA ILE A 498 20186 22695 20926 3210 -1503 -2995 C +ATOM 3633 C ILE A 498 62.251 97.620 -4.004 1.00173.82 C +ANISOU 3633 C ILE A 498 21298 23465 21280 3659 -1379 -2903 C +ATOM 3634 O ILE A 498 63.315 97.034 -3.789 1.00168.12 O +ANISOU 3634 O ILE A 498 20732 22684 20462 3692 -1544 -2980 O +ATOM 3635 CB ILE A 498 62.227 99.564 -5.520 1.00165.53 C +ANISOU 3635 CB ILE A 498 19934 22420 20540 3287 -1855 -3283 C +ATOM 3636 CG1 ILE A 498 61.848 100.100 -6.901 1.00171.37 C +ANISOU 3636 CG1 ILE A 498 20360 23137 21618 2911 -1947 -3365 C +ATOM 3637 CG2 ILE A 498 63.706 99.798 -5.239 1.00153.61 C +ANISOU 3637 CG2 ILE A 498 18613 20820 18931 3398 -2256 -3525 C +ATOM 3638 CD1 ILE A 498 61.891 101.605 -6.996 1.00174.73 C +ANISOU 3638 CD1 ILE A 498 20793 23594 22004 2996 -2195 -3587 C +ATOM 3639 N SER A 499 61.362 97.890 -3.054 1.00187.32 N +ANISOU 3639 N SER A 499 23169 25252 22753 4049 -1074 -2727 N +ATOM 3640 CA SER A 499 61.558 97.444 -1.683 1.00198.47 C +ANISOU 3640 CA SER A 499 24999 26682 23729 4594 -880 -2593 C +ATOM 3641 C SER A 499 61.611 95.926 -1.651 1.00206.95 C +ANISOU 3641 C SER A 499 26003 27687 24943 4481 -548 -2315 C +ATOM 3642 O SER A 499 62.302 95.334 -0.823 1.00211.11 O +ANISOU 3642 O SER A 499 26867 28195 25151 4806 -533 -2293 O +ATOM 3643 CB SER A 499 60.435 97.952 -0.780 1.00200.16 C +ANISOU 3643 CB SER A 499 25385 26976 23690 5064 -498 -2368 C +ATOM 3644 OG SER A 499 60.635 97.541 0.556 1.00203.12 O +ANISOU 3644 OG SER A 499 26244 27359 23574 5697 -286 -2224 O +ATOM 3645 N ASP A 500 60.877 95.300 -2.564 1.00209.43 N +ANISOU 3645 N ASP A 500 25883 27938 25751 4040 -320 -2123 N +ATOM 3646 CA ASP A 500 60.862 93.851 -2.655 1.00212.36 C +ANISOU 3646 CA ASP A 500 26120 28207 26361 3883 -39 -1865 C +ATOM 3647 C ASP A 500 62.097 93.307 -3.366 1.00203.21 C +ANISOU 3647 C ASP A 500 24946 26999 25265 3557 -426 -2093 C +ATOM 3648 O ASP A 500 62.511 92.195 -3.092 1.00204.96 O +ANISOU 3648 O ASP A 500 25238 27165 25472 3573 -286 -1954 O +ATOM 3649 CB ASP A 500 59.580 93.355 -3.335 1.00221.23 C +ANISOU 3649 CB ASP A 500 26765 29217 28075 3573 287 -1593 C +ATOM 3650 CG ASP A 500 58.346 93.583 -2.486 1.00233.55 C +ANISOU 3650 CG ASP A 500 28310 30773 29657 3938 816 -1243 C +ATOM 3651 OD1 ASP A 500 58.502 93.807 -1.272 1.00239.09 O +ANISOU 3651 OD1 ASP A 500 29432 31558 29852 4492 1029 -1137 O +ATOM 3652 OD2 ASP A 500 57.226 93.532 -3.034 1.00237.24 O +ANISOU 3652 OD2 ASP A 500 28355 31128 30656 3709 1011 -1072 O +ATOM 3653 N ILE A 501 62.692 94.094 -4.258 1.00192.67 N +ANISOU 3653 N ILE A 501 23525 25673 24008 3292 -875 -2413 N +ATOM 3654 CA ILE A 501 63.864 93.633 -5.007 1.00181.71 C +ANISOU 3654 CA ILE A 501 22108 24215 22719 3006 -1195 -2587 C +ATOM 3655 C ILE A 501 65.165 93.699 -4.196 1.00170.68 C +ANISOU 3655 C ILE A 501 21064 22814 20972 3287 -1450 -2764 C +ATOM 3656 O ILE A 501 65.940 92.735 -4.173 1.00164.70 O +ANISOU 3656 O ILE A 501 20376 21999 20202 3222 -1476 -2736 O +ATOM 3657 CB ILE A 501 64.023 94.385 -6.353 1.00138.03 C +ANISOU 3657 CB ILE A 501 16341 18649 17453 2657 -1507 -2802 C +ATOM 3658 CG1 ILE A 501 63.385 93.589 -7.491 1.00130.33 C +ANISOU 3658 CG1 ILE A 501 15061 17587 16872 2296 -1411 -2683 C +ATOM 3659 CG2 ILE A 501 65.491 94.642 -6.682 1.00137.98 C +ANISOU 3659 CG2 ILE A 501 16437 18581 17410 2596 -1883 -3037 C +ATOM 3660 CD1 ILE A 501 61.877 93.516 -7.426 1.00127.50 C +ANISOU 3660 CD1 ILE A 501 14480 17217 16748 2292 -1111 -2482 C +ATOM 3661 N HIS A 502 65.396 94.829 -3.532 1.00164.00 N +ANISOU 3661 N HIS A 502 20439 22007 19869 3613 -1676 -2964 N +ATOM 3662 CA HIS A 502 66.603 95.022 -2.737 1.00157.69 C +ANISOU 3662 CA HIS A 502 19968 21147 18800 3931 -2029 -3195 C +ATOM 3663 C HIS A 502 66.627 94.018 -1.594 1.00162.64 C +ANISOU 3663 C HIS A 502 20933 21795 19069 4322 -1761 -3012 C +ATOM 3664 O HIS A 502 67.677 93.496 -1.229 1.00164.35 O +ANISOU 3664 O HIS A 502 21348 21935 19163 4433 -1971 -3125 O +ATOM 3665 CB HIS A 502 66.654 96.452 -2.195 1.00150.83 C +ANISOU 3665 CB HIS A 502 19272 20282 17755 4264 -2351 -3452 C +ATOM 3666 CG HIS A 502 67.989 97.117 -2.348 1.00143.78 C +ANISOU 3666 CG HIS A 502 18387 19223 17019 4248 -2929 -3804 C +ATOM 3667 ND1 HIS A 502 68.672 97.155 -3.544 1.00137.00 N +ANISOU 3667 ND1 HIS A 502 17193 18253 16609 3785 -3096 -3872 N +ATOM 3668 CD2 HIS A 502 68.759 97.787 -1.457 1.00140.15 C +ANISOU 3668 CD2 HIS A 502 18217 18647 16387 4674 -3387 -4099 C +ATOM 3669 CE1 HIS A 502 69.807 97.814 -3.383 1.00131.33 C +ANISOU 3669 CE1 HIS A 502 16503 17343 16055 3893 -3583 -4155 C +ATOM 3670 NE2 HIS A 502 69.883 98.208 -2.125 1.00130.34 N +ANISOU 3670 NE2 HIS A 502 16741 17200 15583 4416 -3815 -4326 N +ATOM 3671 N THR A 503 65.450 93.751 -1.039 1.00163.37 N +ANISOU 3671 N THR A 503 21080 21971 19022 4552 -1268 -2705 N +ATOM 3672 CA THR A 503 65.295 92.771 0.033 1.00163.31 C +ANISOU 3672 CA THR A 503 21384 21972 18696 4974 -872 -2437 C +ATOM 3673 C THR A 503 65.393 91.327 -0.475 1.00151.49 C +ANISOU 3673 C THR A 503 19659 20415 17484 4610 -614 -2206 C +ATOM 3674 O THR A 503 65.955 90.464 0.198 1.00146.11 O +ANISOU 3674 O THR A 503 19247 19705 16563 4853 -520 -2130 O +ATOM 3675 CB THR A 503 63.956 92.972 0.783 1.00170.09 C +ANISOU 3675 CB THR A 503 22342 22899 19387 5379 -334 -2106 C +ATOM 3676 OG1 THR A 503 64.026 94.162 1.576 1.00176.61 O +ANISOU 3676 OG1 THR A 503 23551 23775 19778 5902 -585 -2325 O +ATOM 3677 CG2 THR A 503 63.645 91.790 1.693 1.00171.53 C +ANISOU 3677 CG2 THR A 503 22747 23054 19374 5760 235 -1707 C +ATOM 3678 N LYS A 504 64.865 91.071 -1.669 1.00141.41 N +ANISOU 3678 N LYS A 504 17911 19106 16711 4058 -541 -2117 N +ATOM 3679 CA LYS A 504 64.918 89.730 -2.251 1.00138.96 C +ANISOU 3679 CA LYS A 504 17368 18711 16719 3706 -369 -1930 C +ATOM 3680 C LYS A 504 66.335 89.382 -2.646 1.00142.04 C +ANISOU 3680 C LYS A 504 17848 19062 17058 3520 -779 -2178 C +ATOM 3681 O LYS A 504 66.762 88.230 -2.559 1.00134.46 O +ANISOU 3681 O LYS A 504 16927 18053 16108 3463 -666 -2057 O +ATOM 3682 CB LYS A 504 64.010 89.614 -3.477 1.00129.82 C +ANISOU 3682 CB LYS A 504 15721 17488 16116 3223 -303 -1839 C +ATOM 3683 CG LYS A 504 62.585 89.222 -3.164 1.00130.75 C +ANISOU 3683 CG LYS A 504 15616 17544 16518 3307 225 -1458 C +ATOM 3684 N LEU A 505 67.053 90.395 -3.100 1.00138.41 N +ANISOU 3684 N LEU A 505 17394 18602 16593 3427 -1237 -2505 N +ATOM 3685 CA LEU A 505 68.435 90.224 -3.458 1.00120.29 C +ANISOU 3685 CA LEU A 505 15154 16230 14322 3285 -1619 -2726 C +ATOM 3686 C LEU A 505 69.260 89.889 -2.223 1.00124.70 C +ANISOU 3686 C LEU A 505 16126 16770 14484 3724 -1710 -2800 C +ATOM 3687 O LEU A 505 70.166 89.052 -2.259 1.00124.02 O +ANISOU 3687 O LEU A 505 16106 16619 14396 3643 -1808 -2823 O +ATOM 3688 CB LEU A 505 68.966 91.499 -4.066 1.00109.42 C +ANISOU 3688 CB LEU A 505 13673 14807 13094 3171 -2036 -3016 C +ATOM 3689 CG LEU A 505 70.386 91.097 -4.404 1.00128.61 C +ANISOU 3689 CG LEU A 505 16120 17111 15635 3034 -2336 -3157 C +ATOM 3690 CD1 LEU A 505 70.509 91.048 -5.920 1.00141.22 C +ANISOU 3690 CD1 LEU A 505 17393 18648 17616 2580 -2370 -3131 C +ATOM 3691 CD2 LEU A 505 71.441 91.946 -3.655 1.00114.58 C +ANISOU 3691 CD2 LEU A 505 14554 15224 13758 3350 -2784 -3462 C +ATOM 3692 N LEU A 506 68.951 90.579 -1.132 1.00130.40 N +ANISOU 3692 N LEU A 506 17159 17539 14847 4233 -1705 -2854 N +ATOM 3693 CA LEU A 506 69.612 90.336 0.139 1.00131.63 C +ANISOU 3693 CA LEU A 506 17798 17665 14549 4786 -1814 -2947 C +ATOM 3694 C LEU A 506 69.501 88.865 0.507 1.00144.65 C +ANISOU 3694 C LEU A 506 19548 19330 16080 4841 -1380 -2638 C +ATOM 3695 O LEU A 506 70.448 88.287 1.024 1.00156.36 O +ANISOU 3695 O LEU A 506 21304 20754 17351 5038 -1550 -2741 O +ATOM 3696 CB LEU A 506 69.008 91.206 1.240 1.00120.17 C +ANISOU 3696 CB LEU A 506 16713 16270 12675 5409 -1772 -2980 C +ATOM 3697 N ARG A 507 68.351 88.260 0.214 1.00146.01 N +ANISOU 3697 N ARG A 507 19470 19552 16453 4658 -839 -2263 N +ATOM 3698 CA ARG A 507 68.159 86.831 0.443 1.00152.26 C +ANISOU 3698 CA ARG A 507 20261 20320 17271 4649 -394 -1930 C +ATOM 3699 C ARG A 507 69.164 86.004 -0.360 1.00145.14 C +ANISOU 3699 C ARG A 507 19199 19352 16597 4200 -653 -2042 C +ATOM 3700 O ARG A 507 69.471 84.873 0.007 1.00142.95 O +ANISOU 3700 O ARG A 507 19044 19045 16224 4273 -450 -1882 O +ATOM 3701 CB ARG A 507 66.724 86.401 0.111 1.00161.16 C +ANISOU 3701 CB ARG A 507 21023 21434 18777 4463 165 -1523 C +ATOM 3702 CG ARG A 507 65.661 86.975 1.046 1.00177.63 C +ANISOU 3702 CG ARG A 507 23283 23565 20645 4978 585 -1290 C +ATOM 3703 CD ARG A 507 64.263 86.468 0.696 1.00187.68 C +ANISOU 3703 CD ARG A 507 24111 24757 22442 4769 1148 -857 C +ATOM 3704 NE ARG A 507 63.229 87.082 1.527 1.00198.88 N +ANISOU 3704 NE ARG A 507 25661 26202 23704 5259 1588 -600 N +ATOM 3705 CZ ARG A 507 61.923 86.883 1.368 1.00205.71 C +ANISOU 3705 CZ ARG A 507 26137 26967 25058 5169 2095 -210 C +ATOM 3706 NH1 ARG A 507 61.484 86.084 0.406 1.00207.33 N +ANISOU 3706 NH1 ARG A 507 25797 27020 25959 4610 2159 -75 N +ATOM 3707 NH2 ARG A 507 61.055 87.485 2.171 1.00208.11 N +ANISOU 3707 NH2 ARG A 507 26596 27293 25185 5668 2517 41 N +ATOM 3708 N LEU A 508 69.681 86.579 -1.445 1.00134.19 N +ANISOU 3708 N LEU A 508 17555 17933 15496 3776 -1072 -2294 N +ATOM 3709 CA LEU A 508 70.654 85.888 -2.287 1.00125.06 C +ANISOU 3709 CA LEU A 508 16259 16704 14555 3385 -1304 -2382 C +ATOM 3710 C LEU A 508 72.070 85.983 -1.730 1.00133.75 C +ANISOU 3710 C LEU A 508 17660 17739 15419 3609 -1705 -2655 C +ATOM 3711 O LEU A 508 72.775 84.975 -1.650 1.00132.42 O +ANISOU 3711 O LEU A 508 17581 17530 15202 3562 -1700 -2615 O +ATOM 3712 CB LEU A 508 70.606 86.404 -3.726 1.00112.64 C +ANISOU 3712 CB LEU A 508 14312 15095 13392 2907 -1513 -2482 C +ATOM 3713 CG LEU A 508 69.271 86.186 -4.436 1.00113.17 C +ANISOU 3713 CG LEU A 508 14056 15174 13769 2653 -1215 -2260 C +ATOM 3714 CD1 LEU A 508 69.459 86.184 -5.945 1.00106.22 C +ANISOU 3714 CD1 LEU A 508 12898 14225 13237 2216 -1432 -2343 C +ATOM 3715 CD2 LEU A 508 68.613 84.894 -3.968 1.00117.32 C +ANISOU 3715 CD2 LEU A 508 14560 15678 14341 2703 -779 -1932 C +ATOM 3716 N SER A 509 72.491 87.188 -1.355 1.00130.72 N +ANISOU 3716 N SER A 509 17414 17317 14936 3856 -2085 -2944 N +ATOM 3717 CA SER A 509 73.767 87.347 -0.663 1.00136.66 C +ANISOU 3717 CA SER A 509 18457 17948 15519 4156 -2531 -3236 C +ATOM 3718 C SER A 509 73.698 86.583 0.646 1.00143.40 C +ANISOU 3718 C SER A 509 19773 18845 15866 4687 -2319 -3138 C +ATOM 3719 O SER A 509 74.680 85.977 1.082 1.00138.35 O +ANISOU 3719 O SER A 509 19358 18121 15090 4839 -2521 -3256 O +ATOM 3720 CB SER A 509 74.089 88.820 -0.410 1.00130.15 C +ANISOU 3720 CB SER A 509 17685 17028 14738 4382 -3008 -3572 C +ATOM 3721 OG SER A 509 74.991 89.319 -1.384 1.00125.27 O +ANISOU 3721 OG SER A 509 16739 16248 14609 4003 -3376 -3749 O +ATOM 3722 N SER A 510 72.520 86.615 1.263 1.00144.65 N +ANISOU 3722 N SER A 510 20078 19123 15761 4999 -1878 -2899 N +ATOM 3723 CA SER A 510 72.236 85.777 2.417 1.00148.97 C +ANISOU 3723 CA SER A 510 21044 19713 15846 5526 -1487 -2677 C +ATOM 3724 C SER A 510 72.424 84.315 2.046 1.00145.48 C +ANISOU 3724 C SER A 510 20455 19266 15554 5204 -1182 -2426 C +ATOM 3725 O SER A 510 73.001 83.552 2.811 1.00140.49 O +ANISOU 3725 O SER A 510 20177 18606 14598 5539 -1145 -2415 O +ATOM 3726 CB SER A 510 70.807 85.999 2.911 1.00149.05 C +ANISOU 3726 CB SER A 510 21111 19824 15699 5832 -930 -2348 C +ATOM 3727 OG SER A 510 70.346 84.882 3.643 1.00147.27 O +ANISOU 3727 OG SER A 510 21097 19617 15240 6154 -337 -1962 O +ATOM 3728 N SER A 511 71.946 83.946 0.858 1.00139.39 N +ANISOU 3728 N SER A 511 19184 18508 15269 4583 -1004 -2249 N +ATOM 3729 CA SER A 511 72.011 82.573 0.365 1.00129.68 C +ANISOU 3729 CA SER A 511 17763 17254 14255 4236 -745 -2013 C +ATOM 3730 C SER A 511 73.437 82.171 0.006 1.00129.30 C +ANISOU 3730 C SER A 511 17762 17135 14232 4035 -1175 -2258 C +ATOM 3731 O SER A 511 73.792 80.991 0.047 1.00109.17 O +ANISOU 3731 O SER A 511 15254 14565 11659 3957 -1019 -2119 O +ATOM 3732 CB SER A 511 71.108 82.416 -0.862 1.00125.40 C +ANISOU 3732 CB SER A 511 16701 16703 14243 3684 -564 -1828 C +ATOM 3733 OG SER A 511 71.167 81.106 -1.394 1.00137.08 O +ANISOU 3733 OG SER A 511 17990 18125 15969 3359 -392 -1635 O +ATOM 3734 N GLN A 512 74.247 83.164 -0.347 1.00131.12 N +ANISOU 3734 N GLN A 512 17958 17304 14560 3957 -1699 -2603 N +ATOM 3735 CA GLN A 512 75.621 82.941 -0.774 1.00120.52 C +ANISOU 3735 CA GLN A 512 16587 15845 13361 3755 -2110 -2821 C +ATOM 3736 C GLN A 512 76.508 82.796 0.431 1.00123.24 C +ANISOU 3736 C GLN A 512 17385 16120 13322 4264 -2359 -3023 C +ATOM 3737 O GLN A 512 77.569 82.174 0.385 1.00128.94 O +ANISOU 3737 O GLN A 512 18162 16749 14081 4188 -2573 -3124 O +ATOM 3738 CB GLN A 512 76.096 84.150 -1.546 1.00118.44 C +ANISOU 3738 CB GLN A 512 16077 15483 13440 3537 -2531 -3073 C +ATOM 3739 CG GLN A 512 76.798 83.822 -2.807 1.00125.91 C +ANISOU 3739 CG GLN A 512 16711 16343 14787 3048 -2644 -3057 C +ATOM 3740 CD GLN A 512 76.915 85.020 -3.690 1.00137.22 C +ANISOU 3740 CD GLN A 512 17859 17690 16589 2843 -2877 -3186 C +ATOM 3741 OE1 GLN A 512 77.456 86.052 -3.281 1.00153.03 O +ANISOU 3741 OE1 GLN A 512 19904 19568 18672 3053 -3245 -3442 O +ATOM 3742 NE2 GLN A 512 76.353 84.922 -4.897 1.00128.62 N +ANISOU 3742 NE2 GLN A 512 16487 16643 15739 2470 -2678 -3017 N +ATOM 3743 N GLY A 513 76.073 83.422 1.510 1.00128.93 N +ANISOU 3743 N GLY A 513 18453 16869 13665 4826 -2362 -3100 N +ATOM 3744 CA GLY A 513 76.767 83.309 2.761 1.00135.31 C +ANISOU 3744 CA GLY A 513 19790 17602 14021 5449 -2610 -3308 C +ATOM 3745 C GLY A 513 76.597 81.908 3.295 1.00133.10 C +ANISOU 3745 C GLY A 513 19748 17398 13425 5620 -2132 -3010 C +ATOM 3746 O GLY A 513 77.464 81.407 3.988 1.00135.48 O +ANISOU 3746 O GLY A 513 20407 17622 13448 5952 -2344 -3163 O +ATOM 3747 N THR A 514 75.478 81.270 2.971 1.00130.00 N +ANISOU 3747 N THR A 514 19139 17128 13128 5402 -1499 -2584 N +ATOM 3748 CA THR A 514 75.252 79.899 3.409 1.00136.05 C +ANISOU 3748 CA THR A 514 20053 17932 13707 5525 -990 -2248 C +ATOM 3749 C THR A 514 76.125 78.980 2.571 1.00136.91 C +ANISOU 3749 C THR A 514 19914 17993 14111 4991 -1151 -2279 C +ATOM 3750 O THR A 514 76.451 77.859 2.971 1.00136.85 O +ANISOU 3750 O THR A 514 20093 17981 13923 5102 -944 -2139 O +ATOM 3751 CB THR A 514 73.784 79.475 3.234 1.00141.83 C +ANISOU 3751 CB THR A 514 20522 18734 14631 5408 -287 -1768 C +ATOM 3752 OG1 THR A 514 73.591 78.921 1.927 1.00142.93 O +ANISOU 3752 OG1 THR A 514 20113 18858 15336 4676 -233 -1640 O +ATOM 3753 CG2 THR A 514 72.861 80.659 3.413 1.00145.09 C +ANISOU 3753 CG2 THR A 514 20916 19192 15018 5620 -214 -1761 C +ATOM 3754 N ILE A 515 76.491 79.473 1.394 1.00129.08 N +ANISOU 3754 N ILE A 515 18518 16960 13565 4442 -1494 -2442 N +ATOM 3755 CA ILE A 515 77.327 78.735 0.468 1.00119.39 C +ANISOU 3755 CA ILE A 515 17053 15677 12633 3951 -1657 -2465 C +ATOM 3756 C ILE A 515 78.787 78.796 0.895 1.00127.05 C +ANISOU 3756 C ILE A 515 18275 16524 13475 4143 -2161 -2798 C +ATOM 3757 O ILE A 515 79.482 77.778 0.906 1.00128.44 O +ANISOU 3757 O ILE A 515 18530 16671 13600 4065 -2153 -2760 O +ATOM 3758 CB ILE A 515 77.220 79.308 -0.945 1.00110.42 C +ANISOU 3758 CB ILE A 515 15452 14516 11987 3401 -1815 -2500 C +ATOM 3759 CG1 ILE A 515 75.801 79.150 -1.481 1.00109.39 C +ANISOU 3759 CG1 ILE A 515 15038 14466 12060 3177 -1388 -2203 C +ATOM 3760 CG2 ILE A 515 78.168 78.587 -1.861 1.00112.80 C +ANISOU 3760 CG2 ILE A 515 15581 14743 12533 2997 -1979 -2516 C +ATOM 3761 CD1 ILE A 515 75.570 79.879 -2.780 1.00110.01 C +ANISOU 3761 CD1 ILE A 515 14740 14519 12538 2759 -1561 -2268 C +ATOM 3762 N GLU A 516 79.240 79.996 1.247 1.00122.76 N +ANISOU 3762 N GLU A 516 17837 15878 12927 4400 -2627 -3132 N +ATOM 3763 CA GLU A 516 80.636 80.218 1.589 1.00124.18 C +ANISOU 3763 CA GLU A 516 18172 15860 13149 4571 -3208 -3493 C +ATOM 3764 C GLU A 516 81.099 79.274 2.681 1.00126.09 C +ANISOU 3764 C GLU A 516 18894 16095 12918 5029 -3183 -3522 C +ATOM 3765 O GLU A 516 82.117 78.595 2.539 1.00116.84 O +ANISOU 3765 O GLU A 516 17721 14825 11848 4893 -3381 -3605 O +ATOM 3766 CB GLU A 516 80.863 81.656 2.046 1.00135.21 C +ANISOU 3766 CB GLU A 516 19658 17112 14603 4907 -3722 -3854 C +ATOM 3767 CG GLU A 516 82.315 82.045 1.962 1.00146.41 C +ANISOU 3767 CG GLU A 516 20992 18238 16400 4885 -4381 -4215 C +ATOM 3768 CD GLU A 516 82.865 81.775 0.582 1.00151.28 C +ANISOU 3768 CD GLU A 516 21100 18789 17590 4220 -4329 -4073 C +ATOM 3769 OE1 GLU A 516 82.373 82.413 -0.367 1.00146.52 O +ANISOU 3769 OE1 GLU A 516 20133 18221 17316 3867 -4198 -3957 O +ATOM 3770 OE2 GLU A 516 83.763 80.917 0.441 1.00154.24 O +ANISOU 3770 OE2 GLU A 516 21471 19078 18055 4089 -4397 -4065 O +END diff --git a/examples/testdata/4IM2_missing_noid.pdb b/examples/testdata/4IM2_missing_noid.pdb new file mode 100644 index 0000000..5a5ef94 --- /dev/null +++ b/examples/testdata/4IM2_missing_noid.pdb @@ -0,0 +1,524 @@ +TITLE STRUCTURE OF TANK-BINDING KINASE 1 (Truncated test file) +ATOM 3510 N LEU A 468 76.337 90.332 -7.628 1.00168.53 N +ANISOU 3510 N LEU A 468 19760 15191 29082 5189 1248 -1702 N +ATOM 3511 CA LEU A 468 75.576 90.788 -6.476 1.00181.60 C +ANISOU 3511 CA LEU A 468 21073 16927 30998 5158 1421 -2051 C +ATOM 3512 C LEU A 468 76.285 91.971 -5.836 1.00196.57 C +ANISOU 3512 C LEU A 468 23029 18636 33021 5053 1667 -2229 C +ATOM 3513 O LEU A 468 75.727 93.067 -5.733 1.00197.97 O +ANISOU 3513 O LEU A 468 23115 18581 33523 5166 1662 -2304 O +ATOM 3514 CB LEU A 468 75.409 89.671 -5.449 1.00173.72 C +ANISOU 3514 CB LEU A 468 19829 16340 29836 4981 1593 -2304 C +ATOM 3515 CG LEU A 468 74.099 88.882 -5.420 1.00170.58 C +ANISOU 3515 CG LEU A 468 19142 16142 29529 5074 1449 -2345 C +ATOM 3516 CD1 LEU A 468 74.023 87.940 -6.581 1.00167.98 C +ANISOU 3516 CD1 LEU A 468 18951 15858 29015 5180 1156 -2025 C +ATOM 3517 CD2 LEU A 468 73.988 88.096 -4.145 1.00165.16 C +ANISOU 3517 CD2 LEU A 468 18217 15818 28720 4865 1706 -2656 C +ATOM 3518 N ASP A 469 77.529 91.753 -5.429 1.00208.80 N +ANISOU 3518 N ASP A 469 24731 20282 34322 4836 1875 -2296 N +ATOM 3519 CA ASP A 469 78.267 92.785 -4.720 1.00223.11 C +ANISOU 3519 CA ASP A 469 26577 21960 36233 4701 2123 -2500 C +ATOM 3520 C ASP A 469 78.653 93.973 -5.599 1.00231.96 C +ANISOU 3520 C ASP A 469 27954 22654 37525 4817 2037 -2283 C +ATOM 3521 O ASP A 469 78.718 95.100 -5.118 1.00235.71 O +ANISOU 3521 O ASP A 469 28377 22940 38242 4798 2175 -2449 O +ATOM 3522 CB ASP A 469 79.506 92.197 -4.057 1.00224.50 C +ANISOU 3522 CB ASP A 469 26841 22370 36089 4433 2350 -2633 C +ATOM 3523 CG ASP A 469 79.748 92.767 -2.679 1.00229.51 C +ANISOU 3523 CG ASP A 469 27303 23107 36794 4254 2637 -3018 C +ATOM 3524 OD1 ASP A 469 79.958 93.992 -2.564 1.00232.36 O +ANISOU 3524 OD1 ASP A 469 27695 23203 37387 4262 2718 -3093 O +ATOM 3525 OD2 ASP A 469 79.751 91.979 -1.709 1.00229.48 O +ANISOU 3525 OD2 ASP A 469 27145 23450 36598 4098 2779 -3242 O +ATOM 3526 N PHE A 470 78.904 93.735 -6.882 1.00233.88 N +ANISOU 3526 N PHE A 470 28481 22742 37641 4931 1813 -1912 N +ATOM 3527 CA PHE A 470 79.292 94.831 -7.766 1.00237.73 C +ANISOU 3527 CA PHE A 470 29252 22819 38256 5033 1733 -1677 C +ATOM 3528 C PHE A 470 78.103 95.470 -8.467 1.00232.15 C +ANISOU 3528 C PHE A 470 28512 21868 37828 5322 1470 -1512 C +ATOM 3529 O PHE A 470 78.021 96.691 -8.532 1.00230.69 O +ANISOU 3529 O PHE A 470 28371 21372 37909 5402 1493 -1517 O +ATOM 3530 CB PHE A 470 80.344 94.379 -8.784 1.00242.99 C +ANISOU 3530 CB PHE A 470 30297 23410 38616 4976 1661 -1357 C +ATOM 3531 CG PHE A 470 80.663 95.388 -9.839 1.00251.53 C +ANISOU 3531 CG PHE A 470 31709 24074 39788 5089 1550 -1064 C +ATOM 3532 CD1 PHE A 470 81.658 96.315 -9.620 1.00252.90 C +ANISOU 3532 CD1 PHE A 470 32021 24036 40032 4942 1771 -1133 C +ATOM 3533 CD2 PHE A 470 80.005 95.389 -11.059 1.00255.02 C +ANISOU 3533 CD2 PHE A 470 32335 24339 40221 5330 1225 -715 C +ATOM 3534 CE1 PHE A 470 81.981 97.242 -10.575 1.00254.75 C +ANISOU 3534 CE1 PHE A 470 32569 23879 40344 5028 1688 -862 C +ATOM 3535 CE2 PHE A 470 80.321 96.320 -12.027 1.00256.76 C +ANISOU 3535 CE2 PHE A 470 32894 24173 40491 5427 1126 -433 C +ATOM 3536 CZ PHE A 470 81.313 97.248 -11.784 1.00256.57 C +ANISOU 3536 CZ PHE A 470 33006 23929 40550 5272 1365 -504 C +ATOM 3537 N CYS A 471 77.190 94.665 -8.997 1.00176.88 N +ANISOU 3537 N CYS A 471 20955 21812 24441 2225 -3821 -3711 N +ATOM 3538 CA CYS A 471 76.124 95.233 -9.815 1.00176.41 C +ANISOU 3538 CA CYS A 471 20820 21906 24301 2124 -3576 -3614 C +ATOM 3539 C CYS A 471 74.868 95.631 -9.050 1.00176.82 C +ANISOU 3539 C CYS A 471 21026 22206 23952 2247 -3588 -3734 C +ATOM 3540 O CYS A 471 74.632 96.811 -8.854 1.00178.66 O +ANISOU 3540 O CYS A 471 21175 22393 24314 2353 -3749 -3873 O +ATOM 3541 CB CYS A 471 75.805 94.346 -11.019 1.00173.95 C +ANISOU 3541 CB CYS A 471 20521 21694 23877 1925 -3217 -3359 C +ATOM 3542 SG CYS A 471 76.954 94.571 -12.398 1.00273.04 S +ANISOU 3542 SG CYS A 471 32850 33934 36957 1863 -3075 -3146 S +ATOM 3543 N ILE A 472 74.068 94.670 -8.603 1.00173.50 N +ANISOU 3543 N ILE A 472 20813 22023 23085 2253 -3402 -3662 N +ATOM 3544 CA ILE A 472 72.771 95.033 -8.022 1.00171.09 C +ANISOU 3544 CA ILE A 472 20616 21936 22453 2374 -3307 -3694 C +ATOM 3545 C ILE A 472 72.832 96.022 -6.874 1.00177.82 C +ANISOU 3545 C ILE A 472 21591 22750 23222 2698 -3611 -3931 C +ATOM 3546 O ILE A 472 71.992 96.914 -6.776 1.00182.92 O +ANISOU 3546 O ILE A 472 22220 23491 23792 2778 -3598 -3985 O +ATOM 3547 CB ILE A 472 71.979 93.843 -7.518 1.00159.41 C +ANISOU 3547 CB ILE A 472 19318 20653 20599 2393 -3038 -3546 C +ATOM 3548 CG1 ILE A 472 70.708 93.667 -8.341 1.00147.27 C +ANISOU 3548 CG1 ILE A 472 17653 19253 19051 2196 -2745 -3384 C +ATOM 3549 CG2 ILE A 472 71.526 94.090 -6.105 1.00155.95 C +ANISOU 3549 CG2 ILE A 472 19129 20312 19812 2752 -3085 -3637 C +ATOM 3550 CD1 ILE A 472 70.927 92.915 -9.606 1.00135.92 C +ANISOU 3550 CD1 ILE A 472 16097 17752 17794 1929 -2643 -3254 C +ATOM 3551 N ARG A 473 73.812 95.865 -5.996 1.00180.33 N +ANISOU 3551 N ARG A 473 22055 22917 23544 2920 -3920 -4092 N +ATOM 3552 CA ARG A 473 73.860 96.762 -4.877 1.00189.65 C +ANISOU 3552 CA ARG A 473 23415 24034 24610 3307 -4283 -4357 C +ATOM 3553 C ARG A 473 74.328 98.021 -5.558 1.00185.98 C +ANISOU 3553 C ARG A 473 22628 23344 24692 3195 -4530 -4477 C +ATOM 3554 O ARG A 473 73.519 98.885 -5.866 1.00181.76 O +ANISOU 3554 O ARG A 473 22002 22904 24153 3165 -4446 -4474 O +ATOM 3555 CB ARG A 473 74.803 96.287 -3.768 1.00205.24 C +ANISOU 3555 CB ARG A 473 25661 25862 26460 3635 -4629 -4545 C +ATOM 3556 CG ARG A 473 74.468 96.921 -2.433 1.00220.50 C +ANISOU 3556 CG ARG A 473 27955 27814 28011 4171 -4923 -4787 C +ATOM 3557 CD ARG A 473 75.337 96.433 -1.295 1.00232.58 C +ANISOU 3557 CD ARG A 473 29837 29192 29341 4591 -5300 -5003 C +ATOM 3558 NE ARG A 473 76.763 96.602 -1.549 1.00237.46 N +ANISOU 3558 NE ARG A 473 30220 29440 30564 4489 -5773 -5205 N +ATOM 3559 CZ ARG A 473 77.716 95.991 -0.861 1.00240.32 C +ANISOU 3559 CZ ARG A 473 30786 29623 30903 4725 -6098 -5371 C +ATOM 3560 NH1 ARG A 473 77.388 95.172 0.115 1.00241.12 N +ANISOU 3560 NH1 ARG A 473 31370 29900 30344 5100 -5978 -5355 N +ATOM 3561 NH2 ARG A 473 78.988 96.195 -1.159 1.00241.54 N +ANISOU 3561 NH2 ARG A 473 30651 29398 31724 4605 -6518 -5533 N +ATOM 3562 N ASN A 474 75.608 98.073 -5.901 1.00188.74 N +ANISOU 3562 N ASN A 474 22768 23381 25563 3107 -4772 -4534 N +ATOM 3563 CA ASN A 474 76.168 99.264 -6.526 1.00188.61 C +ANISOU 3563 CA ASN A 474 22395 23075 26194 3026 -4977 -4605 C +ATOM 3564 C ASN A 474 75.420 99.892 -7.729 1.00173.98 C +ANISOU 3564 C ASN A 474 20312 21326 24467 2781 -4623 -4414 C +ATOM 3565 O ASN A 474 75.482 101.107 -7.891 1.00176.08 O +ANISOU 3565 O ASN A 474 20374 21426 25104 2827 -4803 -4518 O +ATOM 3566 CB ASN A 474 77.650 99.066 -6.857 1.00199.45 C +ANISOU 3566 CB ASN A 474 23514 24060 28207 2939 -5170 -4595 C +ATOM 3567 CG ASN A 474 78.504 98.809 -5.625 1.00210.23 C +ANISOU 3567 CG ASN A 474 25062 25219 29597 3243 -5689 -4883 C +ATOM 3568 OD1 ASN A 474 78.174 99.240 -4.527 1.00215.26 O +ANISOU 3568 OD1 ASN A 474 25969 25891 29928 3596 -6043 -5160 O +ATOM 3569 ND2 ASN A 474 79.613 98.103 -5.810 1.00212.07 N +ANISOU 3569 ND2 ASN A 474 25176 25222 30179 3148 -5748 -4822 N +ATOM 3570 N ILE A 475 74.722 99.114 -8.562 1.00161.86 N +ANISOU 3570 N ILE A 475 18813 20033 22652 2556 -4165 -4158 N +ATOM 3571 CA ILE A 475 74.022 99.727 -9.709 1.00154.84 C +ANISOU 3571 CA ILE A 475 17755 19216 21861 2390 -3885 -4015 C +ATOM 3572 C ILE A 475 72.785 100.520 -9.312 1.00159.27 C +ANISOU 3572 C ILE A 475 18401 19995 22121 2490 -3891 -4125 C +ATOM 3573 O ILE A 475 72.662 101.687 -9.663 1.00162.60 O +ANISOU 3573 O ILE A 475 18651 20327 22804 2505 -3962 -4186 O +ATOM 3574 CB ILE A 475 73.600 98.727 -10.818 1.00214.93 C +ANISOU 3574 CB ILE A 475 25397 26975 29293 2173 -3475 -3753 C +ATOM 3575 CG1 ILE A 475 74.808 98.227 -11.613 1.00218.74 C +ANISOU 3575 CG1 ILE A 475 25753 27211 30145 2083 -3390 -3585 C +ATOM 3576 CG2 ILE A 475 72.618 99.379 -11.789 1.00210.37 C +ANISOU 3576 CG2 ILE A 475 24743 26509 28681 2097 -3256 -3677 C +ATOM 3577 CD1 ILE A 475 74.446 97.201 -12.664 1.00218.15 C +ANISOU 3577 CD1 ILE A 475 25777 27260 29850 1946 -3056 -3359 C +ATOM 3578 N GLU A 476 71.856 99.893 -8.600 1.00156.45 N +ANISOU 3578 N GLU A 476 18293 19907 21245 2571 -3778 -4120 N +ATOM 3579 CA GLU A 476 70.623 100.594 -8.252 1.00142.72 C +ANISOU 3579 CA GLU A 476 16627 18370 19231 2677 -3720 -4176 C +ATOM 3580 C GLU A 476 70.845 101.560 -7.094 1.00144.41 C +ANISOU 3580 C GLU A 476 16959 18500 19412 3012 -4114 -4440 C +ATOM 3581 O GLU A 476 70.012 102.417 -6.811 1.00142.58 O +ANISOU 3581 O GLU A 476 16768 18380 19024 3141 -4136 -4517 O +ATOM 3582 CB GLU A 476 69.486 99.616 -7.958 1.00128.68 C +ANISOU 3582 CB GLU A 476 15020 16860 17012 2665 -3395 -4018 C +ATOM 3583 CG GLU A 476 69.562 98.905 -6.628 1.00128.73 C +ANISOU 3583 CG GLU A 476 15321 16930 16662 2941 -3433 -4041 C +ATOM 3584 CD GLU A 476 68.465 97.866 -6.484 1.00133.35 C +ANISOU 3584 CD GLU A 476 15992 17719 16955 2900 -3026 -3806 C +ATOM 3585 OE1 GLU A 476 68.285 97.061 -7.422 1.00132.30 O +ANISOU 3585 OE1 GLU A 476 15710 17598 16962 2603 -2816 -3643 O +ATOM 3586 OE2 GLU A 476 67.768 97.860 -5.448 1.00136.37 O +ANISOU 3586 OE2 GLU A 476 16588 18223 17003 3196 -2912 -3773 O +ATOM 3587 N LYS A 477 71.990 101.424 -6.439 1.00148.46 N +ANISOU 3587 N LYS A 477 17534 18788 20087 3179 -4467 -4596 N +ATOM 3588 CA LYS A 477 72.399 102.365 -5.405 1.00154.25 C +ANISOU 3588 CA LYS A 477 18378 19348 20882 3544 -4976 -4907 C +ATOM 3589 C LYS A 477 72.634 103.732 -5.994 1.00159.76 C +ANISOU 3589 C LYS A 477 18754 19833 22117 3467 -5183 -5014 C +ATOM 3590 O LYS A 477 72.568 104.742 -5.292 1.00158.01 O +ANISOU 3590 O LYS A 477 18598 19516 21921 3749 -5568 -5266 O +ATOM 3591 CB LYS A 477 73.667 101.883 -4.706 1.00154.03 C +ANISOU 3591 CB LYS A 477 18446 19057 21022 3730 -5372 -5076 C +ATOM 3592 CG LYS A 477 73.367 100.787 -3.750 1.00149.76 C +ANISOU 3592 CG LYS A 477 18324 18717 19861 3975 -5258 -5042 C +ATOM 3593 CD LYS A 477 74.437 100.554 -2.740 1.00143.39 C +ANISOU 3593 CD LYS A 477 17737 17664 19082 4327 -5762 -5306 C +ATOM 3594 CE LYS A 477 73.739 100.087 -1.488 1.00145.72 C +ANISOU 3594 CE LYS A 477 18569 18186 18613 4805 -5697 -5337 C +ATOM 3595 NZ LYS A 477 74.661 99.556 -0.469 1.00149.42 N +ANISOU 3595 NZ LYS A 477 19366 18470 18936 5206 -6108 -5561 N +ATOM 3596 N THR A 478 72.908 103.757 -7.293 1.00166.45 N +ANISOU 3596 N THR A 478 19271 20589 23384 3124 -4918 -4810 N +ATOM 3597 CA THR A 478 73.186 105.012 -7.968 1.00170.99 C +ANISOU 3597 CA THR A 478 19509 20929 24531 3052 -5023 -4844 C +ATOM 3598 C THR A 478 71.905 105.733 -8.391 1.00172.33 C +ANISOU 3598 C THR A 478 19686 21358 24435 2999 -4780 -4796 C +ATOM 3599 O THR A 478 71.021 105.170 -9.046 1.00163.10 O +ANISOU 3599 O THR A 478 18581 20461 22928 2823 -4356 -4594 O +ATOM 3600 CB THR A 478 74.217 104.856 -9.128 1.00146.93 C +ANISOU 3600 CB THR A 478 16115 17589 22125 2815 -4843 -4626 C +ATOM 3601 OG1 THR A 478 74.873 106.106 -9.369 1.00147.15 O +ANISOU 3601 OG1 THR A 478 15790 17239 22881 2857 -5085 -4705 O +ATOM 3602 CG2 THR A 478 73.562 104.391 -10.425 1.00144.32 C +ANISOU 3602 CG2 THR A 478 15764 17473 21597 2570 -4282 -4318 C +ATOM 3603 N VAL A 479 71.809 106.979 -7.944 1.00177.73 N +ANISOU 3603 N VAL A 479 20309 21929 25291 3181 -5110 -5014 N +ATOM 3604 CA VAL A 479 70.674 107.838 -8.218 1.00175.65 C +ANISOU 3604 CA VAL A 479 20044 21875 24819 3171 -4959 -5013 C +ATOM 3605 C VAL A 479 71.176 109.077 -8.939 1.00177.67 C +ANISOU 3605 C VAL A 479 19928 21830 25749 3101 -5069 -5035 C +ATOM 3606 O VAL A 479 72.353 109.417 -8.840 1.00179.02 O +ANISOU 3606 O VAL A 479 19865 21600 26555 3150 -5387 -5121 O +ATOM 3607 CB VAL A 479 69.983 108.266 -6.909 1.00168.37 C +ANISOU 3607 CB VAL A 479 19442 21113 23418 3524 -5243 -5249 C +ATOM 3608 CG1 VAL A 479 69.008 109.403 -7.169 1.00162.23 C +ANISOU 3608 CG1 VAL A 479 18603 20470 22565 3531 -5176 -5285 C +ATOM 3609 CG2 VAL A 479 69.288 107.080 -6.256 1.00163.89 C +ANISOU 3609 CG2 VAL A 479 19236 20856 22180 3624 -4991 -5142 C +ATOM 3610 N MET A 486 57.931 103.433 -6.150 1.00 92.00 N +ANISOU 3610 N MET A 486 10154 13425 11375 3371 -1523 -3283 N +ATOM 3611 CA MET A 486 56.824 104.104 -5.482 1.00110.61 C +ANISOU 3611 CA MET A 486 12558 15882 13585 3644 -1287 -3161 C +ATOM 3612 C MET A 486 56.182 105.150 -6.389 1.00111.38 C +ANISOU 3612 C MET A 486 12428 16016 13877 3424 -1416 -3308 C +ATOM 3613 O MET A 486 56.000 106.305 -6.002 1.00120.35 O +ANISOU 3613 O MET A 486 13691 17243 14794 3639 -1526 -3430 O +ATOM 3614 CB MET A 486 57.288 104.743 -4.170 1.00120.60 C +ANISOU 3614 CB MET A 486 14253 17221 14347 4174 -1395 -3243 C +ATOM 3615 CG MET A 486 58.405 105.768 -4.316 1.00126.87 C +ANISOU 3615 CG MET A 486 15171 17987 15047 4196 -1944 -3629 C +ATOM 3616 SD MET A 486 58.150 107.169 -3.210 1.00146.78 S +ANISOU 3616 SD MET A 486 18044 20597 17128 4757 -2124 -3782 S +ATOM 3617 CE MET A 486 58.003 106.326 -1.640 1.00102.35 C +ANISOU 3617 CE MET A 486 12883 15002 11003 5391 -1797 -3524 C +ATOM 3618 N GLY A 496 57.154 99.412 -2.065 1.00157.62 N +ANISOU 3618 N GLY A 496 19150 21656 19083 4616 82 -2024 N +ATOM 3619 CA GLY A 496 57.031 99.217 -3.498 1.00158.07 C +ANISOU 3619 CA GLY A 496 18749 21636 19677 3991 -116 -2141 C +ATOM 3620 C GLY A 496 57.664 97.914 -3.942 1.00163.71 C +ANISOU 3620 C GLY A 496 19349 22235 20619 3709 -140 -2093 C +ATOM 3621 O GLY A 496 58.040 97.090 -3.111 1.00167.17 O +ANISOU 3621 O GLY A 496 20009 22652 20858 3970 79 -1915 O +ATOM 3622 N GLU A 497 57.778 97.722 -5.254 1.00166.67 N +ANISOU 3622 N GLU A 497 19413 22530 21383 3222 -403 -2250 N +ATOM 3623 CA GLU A 497 58.431 96.534 -5.792 1.00170.73 C +ANISOU 3623 CA GLU A 497 19840 22933 22098 2955 -485 -2239 C +ATOM 3624 C GLU A 497 59.931 96.621 -5.556 1.00172.64 C +ANISOU 3624 C GLU A 497 20403 23228 21965 3043 -809 -2472 C +ATOM 3625 O GLU A 497 60.589 95.609 -5.311 1.00173.01 O +ANISOU 3625 O GLU A 497 20547 23223 21965 3047 -764 -2395 O +ATOM 3626 CB GLU A 497 58.173 96.394 -7.290 1.00169.62 C +ANISOU 3626 CB GLU A 497 19361 22681 22408 2505 -727 -2374 C +ATOM 3627 CG GLU A 497 56.882 97.012 -7.777 1.00173.11 C +ANISOU 3627 CG GLU A 497 19518 23086 23171 2413 -663 -2350 C +ATOM 3628 CD GLU A 497 56.703 96.840 -9.270 1.00173.61 C +ANISOU 3628 CD GLU A 497 19329 23013 23621 2054 -974 -2531 C +ATOM 3629 OE1 GLU A 497 57.597 96.241 -9.908 1.00170.78 O +ANISOU 3629 OE1 GLU A 497 19040 22600 23250 1901 -1202 -2647 O +ATOM 3630 OE2 GLU A 497 55.672 97.300 -9.805 1.00174.73 O +ANISOU 3630 OE2 GLU A 497 19233 23093 24064 1967 -999 -2559 O +ATOM 3631 N ILE A 498 60.466 97.836 -5.652 1.00171.44 N +ANISOU 3631 N ILE A 498 20381 23149 21608 3107 -1147 -2757 N +ATOM 3632 CA ILE A 498 61.883 98.078 -5.403 1.00167.93 C +ANISOU 3632 CA ILE A 498 20186 22695 20926 3210 -1503 -2995 C +ATOM 3633 C ILE A 498 62.251 97.620 -4.004 1.00173.82 C +ANISOU 3633 C ILE A 498 21298 23465 21280 3659 -1379 -2903 C +ATOM 3634 O ILE A 498 63.315 97.034 -3.789 1.00168.12 O +ANISOU 3634 O ILE A 498 20732 22684 20462 3692 -1544 -2980 O +ATOM 3635 CB ILE A 498 62.227 99.564 -5.520 1.00165.53 C +ANISOU 3635 CB ILE A 498 19934 22420 20540 3287 -1855 -3283 C +ATOM 3636 CG1 ILE A 498 61.848 100.100 -6.901 1.00171.37 C +ANISOU 3636 CG1 ILE A 498 20360 23137 21618 2911 -1947 -3365 C +ATOM 3637 CG2 ILE A 498 63.706 99.798 -5.239 1.00153.61 C +ANISOU 3637 CG2 ILE A 498 18613 20820 18931 3398 -2256 -3525 C +ATOM 3638 CD1 ILE A 498 61.891 101.605 -6.996 1.00174.73 C +ANISOU 3638 CD1 ILE A 498 20793 23594 22004 2996 -2195 -3587 C +ATOM 3639 N SER A 499 61.362 97.890 -3.054 1.00187.32 N +ANISOU 3639 N SER A 499 23169 25252 22753 4049 -1074 -2727 N +ATOM 3640 CA SER A 499 61.558 97.444 -1.683 1.00198.47 C +ANISOU 3640 CA SER A 499 24999 26682 23729 4594 -880 -2593 C +ATOM 3641 C SER A 499 61.611 95.926 -1.651 1.00206.95 C +ANISOU 3641 C SER A 499 26003 27687 24943 4481 -548 -2315 C +ATOM 3642 O SER A 499 62.302 95.334 -0.823 1.00211.11 O +ANISOU 3642 O SER A 499 26867 28195 25151 4806 -533 -2293 O +ATOM 3643 CB SER A 499 60.435 97.952 -0.780 1.00200.16 C +ANISOU 3643 CB SER A 499 25385 26976 23690 5064 -498 -2368 C +ATOM 3644 OG SER A 499 60.635 97.541 0.556 1.00203.12 O +ANISOU 3644 OG SER A 499 26244 27359 23574 5697 -286 -2224 O +ATOM 3645 N ASP A 500 60.877 95.300 -2.564 1.00209.43 N +ANISOU 3645 N ASP A 500 25883 27938 25751 4040 -320 -2123 N +ATOM 3646 CA ASP A 500 60.862 93.851 -2.655 1.00212.36 C +ANISOU 3646 CA ASP A 500 26120 28207 26361 3883 -39 -1865 C +ATOM 3647 C ASP A 500 62.097 93.307 -3.366 1.00203.21 C +ANISOU 3647 C ASP A 500 24946 26999 25265 3557 -426 -2093 C +ATOM 3648 O ASP A 500 62.511 92.195 -3.092 1.00204.96 O +ANISOU 3648 O ASP A 500 25238 27165 25472 3573 -286 -1954 O +ATOM 3649 CB ASP A 500 59.580 93.355 -3.335 1.00221.23 C +ANISOU 3649 CB ASP A 500 26765 29217 28075 3573 287 -1593 C +ATOM 3650 CG ASP A 500 58.346 93.583 -2.486 1.00233.55 C +ANISOU 3650 CG ASP A 500 28310 30773 29657 3938 816 -1243 C +ATOM 3651 OD1 ASP A 500 58.502 93.807 -1.272 1.00239.09 O +ANISOU 3651 OD1 ASP A 500 29432 31558 29852 4492 1029 -1137 O +ATOM 3652 OD2 ASP A 500 57.226 93.532 -3.034 1.00237.24 O +ANISOU 3652 OD2 ASP A 500 28355 31128 30656 3709 1011 -1072 O +ATOM 3653 N ILE A 501 62.692 94.094 -4.258 1.00192.67 N +ANISOU 3653 N ILE A 501 23525 25673 24008 3292 -875 -2413 N +ATOM 3654 CA ILE A 501 63.864 93.633 -5.007 1.00181.71 C +ANISOU 3654 CA ILE A 501 22108 24215 22719 3006 -1195 -2587 C +ATOM 3655 C ILE A 501 65.165 93.699 -4.196 1.00170.68 C +ANISOU 3655 C ILE A 501 21064 22814 20972 3287 -1450 -2764 C +ATOM 3656 O ILE A 501 65.940 92.735 -4.173 1.00164.70 O +ANISOU 3656 O ILE A 501 20376 21999 20202 3222 -1476 -2736 O +ATOM 3657 CB ILE A 501 64.023 94.385 -6.353 1.00138.03 C +ANISOU 3657 CB ILE A 501 16341 18649 17453 2657 -1507 -2802 C +ATOM 3658 CG1 ILE A 501 63.385 93.589 -7.491 1.00130.33 C +ANISOU 3658 CG1 ILE A 501 15061 17587 16872 2296 -1411 -2683 C +ATOM 3659 CG2 ILE A 501 65.491 94.642 -6.682 1.00137.98 C +ANISOU 3659 CG2 ILE A 501 16437 18581 17410 2596 -1883 -3037 C +ATOM 3660 CD1 ILE A 501 61.877 93.516 -7.426 1.00127.50 C +ANISOU 3660 CD1 ILE A 501 14480 17217 16748 2292 -1111 -2482 C +ATOM 3661 N HIS A 502 65.396 94.829 -3.532 1.00164.00 N +ANISOU 3661 N HIS A 502 20439 22007 19869 3613 -1676 -2964 N +ATOM 3662 CA HIS A 502 66.603 95.022 -2.737 1.00157.69 C +ANISOU 3662 CA HIS A 502 19968 21147 18800 3931 -2029 -3195 C +ATOM 3663 C HIS A 502 66.627 94.018 -1.594 1.00162.64 C +ANISOU 3663 C HIS A 502 20933 21795 19069 4322 -1761 -3012 C +ATOM 3664 O HIS A 502 67.677 93.496 -1.229 1.00164.35 O +ANISOU 3664 O HIS A 502 21348 21935 19163 4433 -1971 -3125 O +ATOM 3665 CB HIS A 502 66.654 96.452 -2.195 1.00150.83 C +ANISOU 3665 CB HIS A 502 19272 20282 17755 4264 -2351 -3452 C +ATOM 3666 CG HIS A 502 67.989 97.117 -2.348 1.00143.78 C +ANISOU 3666 CG HIS A 502 18387 19223 17019 4248 -2929 -3804 C +ATOM 3667 ND1 HIS A 502 68.672 97.155 -3.544 1.00137.00 N +ANISOU 3667 ND1 HIS A 502 17193 18253 16609 3785 -3096 -3872 N +ATOM 3668 CD2 HIS A 502 68.759 97.787 -1.457 1.00140.15 C +ANISOU 3668 CD2 HIS A 502 18217 18647 16387 4674 -3387 -4099 C +ATOM 3669 CE1 HIS A 502 69.807 97.814 -3.383 1.00131.33 C +ANISOU 3669 CE1 HIS A 502 16503 17343 16055 3893 -3583 -4155 C +ATOM 3670 NE2 HIS A 502 69.883 98.208 -2.125 1.00130.34 N +ANISOU 3670 NE2 HIS A 502 16741 17200 15583 4416 -3815 -4326 N +ATOM 3671 N THR A 503 65.450 93.751 -1.039 1.00163.37 N +ANISOU 3671 N THR A 503 21080 21971 19022 4552 -1268 -2705 N +ATOM 3672 CA THR A 503 65.295 92.771 0.033 1.00163.31 C +ANISOU 3672 CA THR A 503 21384 21972 18696 4974 -872 -2437 C +ATOM 3673 C THR A 503 65.393 91.327 -0.475 1.00151.49 C +ANISOU 3673 C THR A 503 19659 20415 17484 4610 -614 -2206 C +ATOM 3674 O THR A 503 65.955 90.464 0.198 1.00146.11 O +ANISOU 3674 O THR A 503 19247 19705 16563 4853 -520 -2130 O +ATOM 3675 CB THR A 503 63.956 92.972 0.783 1.00170.09 C +ANISOU 3675 CB THR A 503 22342 22899 19387 5379 -334 -2106 C +ATOM 3676 OG1 THR A 503 64.026 94.162 1.576 1.00176.61 O +ANISOU 3676 OG1 THR A 503 23551 23775 19778 5902 -585 -2325 O +ATOM 3677 CG2 THR A 503 63.645 91.790 1.693 1.00171.53 C +ANISOU 3677 CG2 THR A 503 22747 23054 19374 5760 235 -1707 C +ATOM 3678 N LYS A 504 64.865 91.071 -1.669 1.00141.41 N +ANISOU 3678 N LYS A 504 17911 19106 16711 4058 -541 -2117 N +ATOM 3679 CA LYS A 504 64.918 89.730 -2.251 1.00138.96 C +ANISOU 3679 CA LYS A 504 17368 18711 16719 3706 -369 -1930 C +ATOM 3680 C LYS A 504 66.335 89.382 -2.646 1.00142.04 C +ANISOU 3680 C LYS A 504 17848 19062 17058 3520 -779 -2178 C +ATOM 3681 O LYS A 504 66.762 88.230 -2.559 1.00134.46 O +ANISOU 3681 O LYS A 504 16927 18053 16108 3463 -666 -2057 O +ATOM 3682 CB LYS A 504 64.010 89.614 -3.477 1.00129.82 C +ANISOU 3682 CB LYS A 504 15721 17488 16116 3223 -303 -1839 C +ATOM 3683 CG LYS A 504 62.585 89.222 -3.164 1.00130.75 C +ANISOU 3683 CG LYS A 504 15616 17544 16518 3307 225 -1458 C +ATOM 3684 N LEU A 505 67.053 90.395 -3.100 1.00138.41 N +ANISOU 3684 N LEU A 505 17394 18602 16593 3427 -1237 -2505 N +ATOM 3685 CA LEU A 505 68.435 90.224 -3.458 1.00120.29 C +ANISOU 3685 CA LEU A 505 15154 16230 14322 3285 -1619 -2726 C +ATOM 3686 C LEU A 505 69.260 89.889 -2.223 1.00124.70 C +ANISOU 3686 C LEU A 505 16126 16770 14484 3724 -1710 -2800 C +ATOM 3687 O LEU A 505 70.166 89.052 -2.259 1.00124.02 O +ANISOU 3687 O LEU A 505 16106 16619 14396 3643 -1808 -2823 O +ATOM 3688 CB LEU A 505 68.966 91.499 -4.066 1.00109.42 C +ANISOU 3688 CB LEU A 505 13673 14807 13094 3171 -2036 -3016 C +ATOM 3689 CG LEU A 505 70.386 91.097 -4.404 1.00128.61 C +ANISOU 3689 CG LEU A 505 16120 17111 15635 3034 -2336 -3157 C +ATOM 3690 CD1 LEU A 505 70.509 91.048 -5.920 1.00141.22 C +ANISOU 3690 CD1 LEU A 505 17393 18648 17616 2580 -2370 -3131 C +ATOM 3691 CD2 LEU A 505 71.441 91.946 -3.655 1.00114.58 C +ANISOU 3691 CD2 LEU A 505 14554 15224 13758 3350 -2784 -3462 C +ATOM 3692 N LEU A 506 68.951 90.579 -1.132 1.00130.40 N +ANISOU 3692 N LEU A 506 17159 17539 14847 4233 -1705 -2854 N +ATOM 3693 CA LEU A 506 69.612 90.336 0.139 1.00131.63 C +ANISOU 3693 CA LEU A 506 17798 17665 14549 4786 -1814 -2947 C +ATOM 3694 C LEU A 506 69.501 88.865 0.507 1.00144.65 C +ANISOU 3694 C LEU A 506 19548 19330 16080 4841 -1380 -2638 C +ATOM 3695 O LEU A 506 70.448 88.287 1.024 1.00156.36 O +ANISOU 3695 O LEU A 506 21304 20754 17351 5038 -1550 -2741 O +ATOM 3696 CB LEU A 506 69.008 91.206 1.240 1.00120.17 C +ANISOU 3696 CB LEU A 506 16713 16270 12675 5409 -1772 -2980 C +ATOM 3697 N ARG A 507 68.351 88.260 0.214 1.00146.01 N +ANISOU 3697 N ARG A 507 19470 19552 16453 4658 -839 -2263 N +ATOM 3698 CA ARG A 507 68.159 86.831 0.443 1.00152.26 C +ANISOU 3698 CA ARG A 507 20261 20320 17271 4649 -394 -1930 C +ATOM 3699 C ARG A 507 69.164 86.004 -0.360 1.00145.14 C +ANISOU 3699 C ARG A 507 19199 19352 16597 4200 -653 -2042 C +ATOM 3700 O ARG A 507 69.471 84.873 0.007 1.00142.95 O +ANISOU 3700 O ARG A 507 19044 19045 16224 4273 -450 -1882 O +ATOM 3701 CB ARG A 507 66.724 86.401 0.111 1.00161.16 C +ANISOU 3701 CB ARG A 507 21023 21434 18777 4463 165 -1523 C +ATOM 3702 CG ARG A 507 65.661 86.975 1.046 1.00177.63 C +ANISOU 3702 CG ARG A 507 23283 23565 20645 4978 585 -1290 C +ATOM 3703 CD ARG A 507 64.263 86.468 0.696 1.00187.68 C +ANISOU 3703 CD ARG A 507 24111 24757 22442 4769 1148 -857 C +ATOM 3704 NE ARG A 507 63.229 87.082 1.527 1.00198.88 N +ANISOU 3704 NE ARG A 507 25661 26202 23704 5259 1588 -600 N +ATOM 3705 CZ ARG A 507 61.923 86.883 1.368 1.00205.71 C +ANISOU 3705 CZ ARG A 507 26137 26967 25058 5169 2095 -210 C +ATOM 3706 NH1 ARG A 507 61.484 86.084 0.406 1.00207.33 N +ANISOU 3706 NH1 ARG A 507 25797 27020 25959 4610 2159 -75 N +ATOM 3707 NH2 ARG A 507 61.055 87.485 2.171 1.00208.11 N +ANISOU 3707 NH2 ARG A 507 26596 27293 25185 5668 2517 41 N +ATOM 3708 N LEU A 508 69.681 86.579 -1.445 1.00134.19 N +ANISOU 3708 N LEU A 508 17555 17933 15496 3776 -1072 -2294 N +ATOM 3709 CA LEU A 508 70.654 85.888 -2.287 1.00125.06 C +ANISOU 3709 CA LEU A 508 16259 16704 14555 3385 -1304 -2382 C +ATOM 3710 C LEU A 508 72.070 85.983 -1.730 1.00133.75 C +ANISOU 3710 C LEU A 508 17660 17739 15419 3609 -1705 -2655 C +ATOM 3711 O LEU A 508 72.775 84.975 -1.650 1.00132.42 O +ANISOU 3711 O LEU A 508 17581 17530 15202 3562 -1700 -2615 O +ATOM 3712 CB LEU A 508 70.606 86.404 -3.726 1.00112.64 C +ANISOU 3712 CB LEU A 508 14312 15095 13392 2907 -1513 -2482 C +ATOM 3713 CG LEU A 508 69.271 86.186 -4.436 1.00113.17 C +ANISOU 3713 CG LEU A 508 14056 15174 13769 2653 -1215 -2260 C +ATOM 3714 CD1 LEU A 508 69.459 86.184 -5.945 1.00106.22 C +ANISOU 3714 CD1 LEU A 508 12898 14225 13237 2216 -1432 -2343 C +ATOM 3715 CD2 LEU A 508 68.613 84.894 -3.968 1.00117.32 C +ANISOU 3715 CD2 LEU A 508 14560 15678 14341 2703 -779 -1932 C +ATOM 3716 N SER A 509 72.491 87.188 -1.355 1.00130.72 N +ANISOU 3716 N SER A 509 17414 17317 14936 3856 -2085 -2944 N +ATOM 3717 CA SER A 509 73.767 87.347 -0.663 1.00136.66 C +ANISOU 3717 CA SER A 509 18457 17948 15519 4156 -2531 -3236 C +ATOM 3718 C SER A 509 73.698 86.583 0.646 1.00143.40 C +ANISOU 3718 C SER A 509 19773 18845 15866 4687 -2319 -3138 C +ATOM 3719 O SER A 509 74.680 85.977 1.082 1.00138.35 O +ANISOU 3719 O SER A 509 19358 18121 15090 4839 -2521 -3256 O +ATOM 3720 CB SER A 509 74.089 88.820 -0.410 1.00130.15 C +ANISOU 3720 CB SER A 509 17685 17028 14738 4382 -3008 -3572 C +ATOM 3721 OG SER A 509 74.991 89.319 -1.384 1.00125.27 O +ANISOU 3721 OG SER A 509 16739 16248 14609 4003 -3376 -3749 O +ATOM 3722 N SER A 510 72.520 86.615 1.263 1.00144.65 N +ANISOU 3722 N SER A 510 20078 19123 15761 4999 -1878 -2899 N +ATOM 3723 CA SER A 510 72.236 85.777 2.417 1.00148.97 C +ANISOU 3723 CA SER A 510 21044 19713 15846 5526 -1487 -2677 C +ATOM 3724 C SER A 510 72.424 84.315 2.046 1.00145.48 C +ANISOU 3724 C SER A 510 20455 19266 15554 5204 -1182 -2426 C +ATOM 3725 O SER A 510 73.001 83.552 2.811 1.00140.49 O +ANISOU 3725 O SER A 510 20177 18606 14598 5539 -1145 -2415 O +ATOM 3726 CB SER A 510 70.807 85.999 2.911 1.00149.05 C +ANISOU 3726 CB SER A 510 21111 19824 15699 5832 -930 -2348 C +ATOM 3727 OG SER A 510 70.346 84.882 3.643 1.00147.27 O +ANISOU 3727 OG SER A 510 21097 19617 15240 6154 -337 -1962 O +ATOM 3728 N SER A 511 71.946 83.946 0.858 1.00139.39 N +ANISOU 3728 N SER A 511 19184 18508 15269 4583 -1004 -2249 N +ATOM 3729 CA SER A 511 72.011 82.573 0.365 1.00129.68 C +ANISOU 3729 CA SER A 511 17763 17254 14255 4236 -745 -2013 C +ATOM 3730 C SER A 511 73.437 82.171 0.006 1.00129.30 C +ANISOU 3730 C SER A 511 17762 17135 14232 4035 -1175 -2258 C +ATOM 3731 O SER A 511 73.792 80.991 0.047 1.00109.17 O +ANISOU 3731 O SER A 511 15254 14565 11659 3957 -1019 -2119 O +ATOM 3732 CB SER A 511 71.108 82.416 -0.862 1.00125.40 C +ANISOU 3732 CB SER A 511 16701 16703 14243 3684 -564 -1828 C +ATOM 3733 OG SER A 511 71.167 81.106 -1.394 1.00137.08 O +ANISOU 3733 OG SER A 511 17990 18125 15969 3359 -392 -1635 O +ATOM 3734 N GLN A 512 74.247 83.164 -0.347 1.00131.12 N +ANISOU 3734 N GLN A 512 17958 17304 14560 3957 -1699 -2603 N +ATOM 3735 CA GLN A 512 75.621 82.941 -0.774 1.00120.52 C +ANISOU 3735 CA GLN A 512 16587 15845 13361 3755 -2110 -2821 C +ATOM 3736 C GLN A 512 76.508 82.796 0.431 1.00123.24 C +ANISOU 3736 C GLN A 512 17385 16120 13322 4264 -2359 -3023 C +ATOM 3737 O GLN A 512 77.569 82.174 0.385 1.00128.94 O +ANISOU 3737 O GLN A 512 18162 16749 14081 4188 -2573 -3124 O +ATOM 3738 CB GLN A 512 76.096 84.150 -1.546 1.00118.44 C +ANISOU 3738 CB GLN A 512 16077 15483 13440 3537 -2531 -3073 C +ATOM 3739 CG GLN A 512 76.798 83.822 -2.807 1.00125.91 C +ANISOU 3739 CG GLN A 512 16711 16343 14787 3048 -2644 -3057 C +ATOM 3740 CD GLN A 512 76.915 85.020 -3.690 1.00137.22 C +ANISOU 3740 CD GLN A 512 17859 17690 16589 2843 -2877 -3186 C +ATOM 3741 OE1 GLN A 512 77.456 86.052 -3.281 1.00153.03 O +ANISOU 3741 OE1 GLN A 512 19904 19568 18672 3053 -3245 -3442 O +ATOM 3742 NE2 GLN A 512 76.353 84.922 -4.897 1.00128.62 N +ANISOU 3742 NE2 GLN A 512 16487 16643 15739 2470 -2678 -3017 N +ATOM 3743 N GLY A 513 76.073 83.422 1.510 1.00128.93 N +ANISOU 3743 N GLY A 513 18453 16869 13665 4826 -2362 -3100 N +ATOM 3744 CA GLY A 513 76.767 83.309 2.761 1.00135.31 C +ANISOU 3744 CA GLY A 513 19790 17602 14021 5449 -2610 -3308 C +ATOM 3745 C GLY A 513 76.597 81.908 3.295 1.00133.10 C +ANISOU 3745 C GLY A 513 19748 17398 13425 5620 -2132 -3010 C +ATOM 3746 O GLY A 513 77.464 81.407 3.988 1.00135.48 O +ANISOU 3746 O GLY A 513 20407 17622 13448 5952 -2344 -3163 O +ATOM 3747 N THR A 514 75.478 81.270 2.971 1.00130.00 N +ANISOU 3747 N THR A 514 19139 17128 13128 5402 -1499 -2584 N +ATOM 3748 CA THR A 514 75.252 79.899 3.409 1.00136.05 C +ANISOU 3748 CA THR A 514 20053 17932 13707 5525 -990 -2248 C +ATOM 3749 C THR A 514 76.125 78.980 2.571 1.00136.91 C +ANISOU 3749 C THR A 514 19914 17993 14111 4991 -1151 -2279 C +ATOM 3750 O THR A 514 76.451 77.859 2.971 1.00136.85 O +ANISOU 3750 O THR A 514 20093 17981 13923 5102 -944 -2139 O +ATOM 3751 CB THR A 514 73.784 79.475 3.234 1.00141.83 C +ANISOU 3751 CB THR A 514 20522 18734 14631 5408 -287 -1768 C +ATOM 3752 OG1 THR A 514 73.591 78.921 1.927 1.00142.93 O +ANISOU 3752 OG1 THR A 514 20113 18858 15336 4676 -233 -1640 O +ATOM 3753 CG2 THR A 514 72.861 80.659 3.413 1.00145.09 C +ANISOU 3753 CG2 THR A 514 20916 19192 15018 5620 -214 -1761 C +ATOM 3754 N ILE A 515 76.491 79.473 1.394 1.00129.08 N +ANISOU 3754 N ILE A 515 18518 16960 13565 4442 -1494 -2442 N +ATOM 3755 CA ILE A 515 77.327 78.735 0.468 1.00119.39 C +ANISOU 3755 CA ILE A 515 17053 15677 12633 3951 -1657 -2465 C +ATOM 3756 C ILE A 515 78.787 78.796 0.895 1.00127.05 C +ANISOU 3756 C ILE A 515 18275 16524 13475 4143 -2161 -2798 C +ATOM 3757 O ILE A 515 79.482 77.778 0.906 1.00128.44 O +ANISOU 3757 O ILE A 515 18530 16671 13600 4065 -2153 -2760 O +ATOM 3758 CB ILE A 515 77.220 79.308 -0.945 1.00110.42 C +ANISOU 3758 CB ILE A 515 15452 14516 11987 3401 -1815 -2500 C +ATOM 3759 CG1 ILE A 515 75.801 79.150 -1.481 1.00109.39 C +ANISOU 3759 CG1 ILE A 515 15038 14466 12060 3177 -1388 -2203 C +ATOM 3760 CG2 ILE A 515 78.168 78.587 -1.861 1.00112.80 C +ANISOU 3760 CG2 ILE A 515 15581 14743 12533 2997 -1979 -2516 C +ATOM 3761 CD1 ILE A 515 75.570 79.879 -2.780 1.00110.01 C +ANISOU 3761 CD1 ILE A 515 14740 14519 12538 2759 -1561 -2268 C +ATOM 3762 N GLU A 516 79.240 79.996 1.247 1.00122.76 N +ANISOU 3762 N GLU A 516 17837 15878 12927 4400 -2627 -3132 N +ATOM 3763 CA GLU A 516 80.636 80.218 1.589 1.00124.18 C +ANISOU 3763 CA GLU A 516 18172 15860 13149 4571 -3208 -3493 C +ATOM 3764 C GLU A 516 81.099 79.274 2.681 1.00126.09 C +ANISOU 3764 C GLU A 516 18894 16095 12918 5029 -3183 -3522 C +ATOM 3765 O GLU A 516 82.117 78.595 2.539 1.00116.84 O +ANISOU 3765 O GLU A 516 17721 14825 11848 4893 -3381 -3605 O +ATOM 3766 CB GLU A 516 80.863 81.656 2.046 1.00135.21 C +ANISOU 3766 CB GLU A 516 19658 17112 14603 4907 -3722 -3854 C +ATOM 3767 CG GLU A 516 82.315 82.045 1.962 1.00146.41 C +ANISOU 3767 CG GLU A 516 20992 18238 16400 4885 -4381 -4215 C +ATOM 3768 CD GLU A 516 82.865 81.775 0.582 1.00151.28 C +ANISOU 3768 CD GLU A 516 21100 18789 17590 4220 -4329 -4073 C +ATOM 3769 OE1 GLU A 516 82.373 82.413 -0.367 1.00146.52 O +ANISOU 3769 OE1 GLU A 516 20133 18221 17316 3867 -4198 -3957 O +ATOM 3770 OE2 GLU A 516 83.763 80.917 0.441 1.00154.24 O +ANISOU 3770 OE2 GLU A 516 21471 19078 18055 4089 -4397 -4065 O +END diff --git a/examples/testdata/4IM2_nterm.pdb b/examples/testdata/4IM2_nterm.pdb new file mode 100644 index 0000000..48f7803 --- /dev/null +++ b/examples/testdata/4IM2_nterm.pdb @@ -0,0 +1,139 @@ +HEADER TRANSFERASE/TRANSFERASE INHIBITOR 01-JAN-13 4IM2 +TITLE STRUCTURE OF TANK-BINDING KINASE 1 (Truncated test file) +DBREF 4IM2 A 1 657 UNP Q9UHD2 TBK1_HUMAN 1 657 +SEQADV 4IM2 GLY A -5 UNP Q9UHD2 EXPRESSION TAG +SEQADV 4IM2 SER A -4 UNP Q9UHD2 EXPRESSION TAG +SEQADV 4IM2 GLY A -3 UNP Q9UHD2 EXPRESSION TAG +SEQADV 4IM2 SER A -2 UNP Q9UHD2 EXPRESSION TAG +SEQADV 4IM2 GLY A -1 UNP Q9UHD2 EXPRESSION TAG +SEQADV 4IM2 SER A 0 UNP Q9UHD2 EXPRESSION TAG +SEQRES 1 A 663 GLY SER GLY SER GLY SER MET GLN SER THR SER ASN HIS +ATOM 1 N GLY A -1 126.784 4.226 -23.353 1.00158.13 N +ANISOU 1 N GLY A -1 19370 17517 23197 6628 1162 2075 N +ATOM 2 CA GLY A -1 125.521 4.306 -24.062 1.00150.94 C +ANISOU 2 CA GLY A -1 18746 16231 22374 6153 1277 1996 C +ATOM 3 C GLY A -1 125.742 4.361 -25.557 1.00146.29 C +ANISOU 3 C GLY A -1 18187 15453 21943 5900 1405 1498 C +ATOM 4 O GLY A -1 126.691 4.980 -26.029 1.00150.85 O +ANISOU 4 O GLY A -1 18536 16366 22413 5906 1385 1160 O +ATOM 5 N SER A 0 124.869 3.710 -26.313 1.00137.36 N +ANISOU 5 N SER A 0 17328 13796 21068 5675 1550 1432 N +ATOM 6 CA SER A 0 125.052 3.672 -27.755 1.00139.44 C +ANISOU 6 CA SER A 0 17634 13884 21461 5464 1674 953 C +ATOM 7 C SER A 0 123.846 4.104 -28.574 1.00137.43 C +ANISOU 7 C SER A 0 17591 13478 21149 4975 1714 755 C +ATOM 8 O SER A 0 122.737 4.275 -28.071 1.00128.25 O +ANISOU 8 O SER A 0 16566 12244 19921 4780 1667 988 O +ATOM 9 CB SER A 0 125.578 2.312 -28.214 1.00155.09 C +ANISOU 9 CB SER A 0 19671 15395 23862 5752 1827 887 C +ATOM 10 OG SER A 0 126.993 2.281 -28.131 1.00164.59 O +ANISOU 10 OG SER A 0 20608 16866 25062 6113 1802 781 O +ATOM 11 N MET A 1 124.096 4.270 -29.861 1.00134.87 N +ANISOU 11 N MET A 1 17283 13123 20839 4799 1805 311 N +ATOM 12 CA MET A 1 123.214 5.039 -30.698 1.00125.22 C +ANISOU 12 CA MET A 1 16193 11958 19428 4369 1804 70 C +ATOM 13 C MET A 1 122.885 4.351 -32.006 1.00124.69 C +ANISOU 13 C MET A 1 16293 11510 19572 4225 1942 -293 C +ATOM 14 O MET A 1 123.723 3.686 -32.606 1.00129.61 O +ANISOU 14 O MET A 1 16868 11984 20394 4414 2057 -519 O +ATOM 15 CB MET A 1 123.867 6.392 -30.970 1.00122.51 C +ANISOU 15 CB MET A 1 15675 12140 18732 4254 1762 -132 C +ATOM 16 CG MET A 1 125.074 6.368 -31.866 1.00120.74 C +ANISOU 16 CG MET A 1 15318 12013 18544 4369 1884 -495 C +ATOM 17 SD MET A 1 125.834 7.982 -31.850 1.00196.33 S +ANISOU 17 SD MET A 1 24655 22193 27747 4246 1861 -642 S +ATOM 18 CE MET A 1 126.785 7.896 -30.345 1.00132.53 C +ANISOU 18 CE MET A 1 16264 14397 19693 4639 1728 -355 C +ATOM 19 N GLN A 2 121.643 4.496 -32.440 1.00123.78 N +ANISOU 19 N GLN A 2 16363 11253 19416 3898 1925 -369 N +ATOM 20 CA GLN A 2 121.313 4.161 -33.809 1.00128.51 C +ANISOU 20 CA GLN A 2 17093 11643 20094 3714 2020 -799 C +ATOM 21 C GLN A 2 121.639 5.396 -34.617 1.00121.06 C +ANISOU 21 C GLN A 2 16105 11138 18752 3541 2004 -1065 C +ATOM 22 O GLN A 2 121.959 6.442 -34.059 1.00107.32 O +ANISOU 22 O GLN A 2 14247 9790 16739 3528 1928 -906 O +ATOM 23 CB GLN A 2 119.832 3.845 -33.965 1.00136.11 C +ANISOU 23 CB GLN A 2 18240 12314 21161 3439 1994 -809 C +ATOM 24 CG GLN A 2 119.171 3.277 -32.740 1.00148.45 C +ANISOU 24 CG GLN A 2 19841 13613 22949 3500 1973 -376 C +ATOM 25 CD GLN A 2 117.773 2.799 -33.042 1.00158.28 C +ANISOU 25 CD GLN A 2 21243 14511 24385 3221 1993 -473 C +ATOM 26 OE1 GLN A 2 116.827 3.097 -32.313 1.00163.28 O +ANISOU 26 OE1 GLN A 2 21915 15160 24966 3070 1921 -211 O +ATOM 27 NE2 GLN A 2 117.631 2.057 -34.134 1.00155.02 N +ANISOU 27 NE2 GLN A 2 20902 13797 24200 3144 2095 -885 N +ATOM 28 N SER A 3 121.547 5.280 -35.931 1.00124.66 N +ANISOU 28 N SER A 3 16661 11530 19175 3410 2090 -1474 N +ATOM 29 CA SER A 3 121.742 6.432 -36.781 1.00120.35 C +ANISOU 29 CA SER A 3 16121 11369 18236 3242 2107 -1703 C +ATOM 30 C SER A 3 121.190 6.186 -38.161 1.00125.27 C +ANISOU 30 C SER A 3 16914 11882 18801 3073 2165 -2108 C +ATOM 31 O SER A 3 120.746 5.090 -38.489 1.00135.86 O +ANISOU 31 O SER A 3 18337 12848 20436 3082 2197 -2268 O +ATOM 32 CB SER A 3 123.217 6.751 -36.923 1.00116.70 C +ANISOU 32 CB SER A 3 15475 11153 17711 3435 2220 -1807 C +ATOM 33 OG SER A 3 123.770 5.974 -37.974 1.00112.78 O +ANISOU 33 OG SER A 3 15005 10478 17367 3528 2375 -2175 O +ATOM 34 N THR A 4 121.244 7.232 -38.972 1.00116.48 N +ANISOU 34 N THR A 4 15849 11109 17301 2928 2191 -2281 N +ATOM 35 CA THR A 4 120.978 7.130 -40.394 1.00113.12 C +ANISOU 35 CA THR A 4 15568 10691 16723 2823 2262 -2692 C +ATOM 36 C THR A 4 122.118 7.839 -41.119 1.00116.32 C +ANISOU 36 C THR A 4 15918 11410 16867 2884 2432 -2867 C +ATOM 37 O THR A 4 123.106 8.236 -40.495 1.00117.17 O +ANISOU 37 O THR A 4 15851 11671 16998 3007 2493 -2706 O +ATOM 38 CB THR A 4 119.615 7.756 -40.775 1.00106.20 C +ANISOU 38 CB THR A 4 14854 9927 15568 2568 2121 -2713 C +ATOM 39 OG1 THR A 4 119.774 9.154 -41.043 1.00107.14 O +ANISOU 39 OG1 THR A 4 15001 10451 15258 2480 2138 -2644 O +ATOM 40 CG2 THR A 4 118.597 7.567 -39.657 1.00 99.42 C +ANISOU 40 CG2 THR A 4 13990 8889 14896 2483 1956 -2396 C +ATOM 41 N SER A 5 121.981 7.982 -42.432 1.00116.70 N +ANISOU 41 N SER A 5 16106 11564 16670 2803 2517 -3210 N +ATOM 42 CA SER A 5 122.975 8.671 -43.247 1.00122.60 C +ANISOU 42 CA SER A 5 16837 12604 17144 2839 2721 -3385 C +ATOM 43 C SER A 5 123.317 10.064 -42.713 1.00128.43 C +ANISOU 43 C SER A 5 17501 13666 17630 2768 2748 -3105 C +ATOM 44 O SER A 5 124.481 10.467 -42.712 1.00133.01 O +ANISOU 44 O SER A 5 17934 14410 18193 2852 2925 -3133 O +ATOM 45 CB SER A 5 122.473 8.779 -44.687 1.00127.34 C +ANISOU 45 CB SER A 5 17646 13318 17418 2741 2774 -3729 C +ATOM 46 OG SER A 5 121.108 9.163 -44.712 1.00135.31 O +ANISOU 46 OG SER A 5 18807 14370 18235 2568 2576 -3643 O +ATOM 47 N ASN A 6 122.303 10.783 -42.240 1.00122.78 N +ANISOU 47 N ASN A 6 16868 13033 16750 2608 2581 -2859 N +ATOM 48 CA ASN A 6 122.460 12.194 -41.897 1.00114.44 C +ANISOU 48 CA ASN A 6 15777 12275 15429 2507 2620 -2639 C +ATOM 49 C ASN A 6 122.348 12.537 -40.412 1.00108.55 C +ANISOU 49 C ASN A 6 14870 11544 14832 2498 2474 -2282 C +ATOM 50 O ASN A 6 122.712 13.640 -40.004 1.00110.16 O +ANISOU 50 O ASN A 6 14983 11984 14890 2435 2531 -2135 O +ATOM 51 CB ASN A 6 121.461 13.034 -42.695 1.00113.00 C +ANISOU 51 CB ASN A 6 15832 12254 14850 2339 2583 -2658 C +ATOM 52 CG ASN A 6 121.646 12.886 -44.191 1.00118.14 C +ANISOU 52 CG ASN A 6 16648 12982 15257 2366 2743 -2999 C +ATOM 53 OD1 ASN A 6 122.765 12.952 -44.698 1.00122.16 O +ANISOU 53 OD1 ASN A 6 17097 13580 15737 2449 2985 -3154 O +ATOM 54 ND2 ASN A 6 120.546 12.672 -44.905 1.00118.96 N +ANISOU 54 ND2 ASN A 6 16946 13074 15179 2302 2609 -3139 N +ATOM 55 N HIS A 7 121.845 11.605 -39.608 1.00100.48 N +ANISOU 55 N HIS A 7 13810 10269 14098 2560 2305 -2149 N +ATOM 56 CA HIS A 7 121.657 11.871 -38.183 1.00 91.49 C +ANISOU 56 CA HIS A 7 12535 9160 13066 2569 2162 -1799 C +ATOM 57 C HIS A 7 122.134 10.725 -37.300 1.00 97.74 C +ANISOU 57 C HIS A 7 13180 9725 14232 2793 2117 -1685 C +ATOM 58 O HIS A 7 122.456 9.647 -37.791 1.00101.23 O +ANISOU 58 O HIS A 7 13643 9923 14898 2924 2188 -1876 O +ATOM 59 CB HIS A 7 120.189 12.175 -37.884 1.00 86.30 C +ANISOU 59 CB HIS A 7 12021 8464 12306 2383 1977 -1628 C +ATOM 60 CG HIS A 7 119.634 13.303 -38.694 1.00101.46 C +ANISOU 60 CG HIS A 7 14094 10603 13853 2201 2000 -1699 C +ATOM 61 ND1 HIS A 7 119.265 13.157 -40.015 1.00 99.83 N +ANISOU 61 ND1 HIS A 7 14076 10384 13472 2153 2049 -1976 N +ATOM 62 CD2 HIS A 7 119.401 14.600 -38.379 1.00 89.60 C +ANISOU 62 CD2 HIS A 7 12588 9342 12114 2078 1988 -1527 C +ATOM 63 CE1 HIS A 7 118.823 14.314 -40.476 1.00 97.86 C +ANISOU 63 CE1 HIS A 7 13944 10363 12875 2029 2062 -1939 C +ATOM 64 NE2 HIS A 7 118.896 15.206 -39.503 1.00 93.14 N +ANISOU 64 NE2 HIS A 7 13235 9899 12253 1975 2035 -1667 N +END diff --git a/examples/testdata/example_annot_file.jva b/examples/testdata/example_annot_file.jva index 1779247..6b9faa4 100644 --- a/examples/testdata/example_annot_file.jva +++ b/examples/testdata/example_annot_file.jva @@ -18,5 +18,5 @@ SEQUENCE_GROUP Group_A 30 50 * SEQUENCE_GROUP Group_B 1 351 2-5 SEQUENCE_GROUP Group_C 12 14 -1 seq1 seq2 seq3 PROPERTIES Group_A description=This is the description colour=Helix Propensity pidThreshold=0 outlineColour=red displayBoxes=true displayText=false colourText=false textCol1=black textCol2=black textColThreshold=0 -PROPERTIES Group_B outlineColour=red colour=None +PROPERTIES Group_B outlineColour=green colour=None PROPERTIES Group_C colour=Clustal diff --git a/examples/testdata/test.aln b/examples/testdata/test.aln index 08a7ac3..6582b12 100644 --- a/examples/testdata/test.aln +++ b/examples/testdata/test.aln @@ -1,53 +1,13 @@ CLUSTAL -FER_CAPAA/1-97 -----------------------------------------------------------A -FER_CAPAN/1-144 MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA -FER1_SOLLC/1-144 MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA -Q93XJ9_SOLTU/1-144 MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA -FER1_PEA/1-149 MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA -Q7XA98_TRIPR/1-152 MATT---PALYGTAVSTSFMRRQPVPMSV-ATTTTTKAFPSGFGLKSVSTKRGDLAVAMA -FER1_MESCR/1-148 MAAT--TAALSGATMSTAFAPK--TPPMTAALPTNVGR--ALFGLKS-SASR-GRVTAMA -FER1_SPIOL/1-147 MAAT--TTTMMG--MATTFVPKPQAPPMMAALPSNTGR--SLFGLKT-GSR--GGRMTMA -FER3_RAPSA/1-96 -----------------------------------------------------------A -FER1_ARATH/1-148 MAST----ALSSAIVGTSFIRRSPAPISLRSLPSANTQ--SLFGLKS-GTARGGRVTAMA -FER_BRANA/1-96 -----------------------------------------------------------A -FER2_ARATH/1-148 MAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA -Q93Z60_ARATH/1-118 MAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA -FER1_MAIZE/1-150 MATVLGSPRAPAFFFSSSSLRAAPAPTAV--ALPAAKV--GIMGRSA-SSRR--RLRAQA -O80429_MAIZE/1-140 MAAT---------ALSMSILR---APPPCFSSPLRLRV--AVAKPLA-APMRRQLLRAQA -1A70|/1-97 -----------------------------------------------------------A - -FER_CAPAA/1-97 SYKVKLITPDGPIEFDCPDDVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG -FER_CAPAN/1-144 SYKVKLITPDGPIEFDCPDNVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG -FER1_SOLLC/1-144 SYKVKLITPEGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGSVDQSDG -Q93XJ9_SOLTU/1-144 SYKVKLITPDGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGTVDQSDG -FER1_PEA/1-149 SYKVKLVTPDGTQEFECPSDVYILDHAEEVGIDLPYSCRAGSCSSCAGKVVGGEVDQSDG -Q7XA98_TRIPR/1-152 TYKVKLITPEGPQEFDCPDDVYILDHAEEVGIELPYSCRAGSCSSCAGKVVNGNVNQEDG -FER1_MESCR/1-148 AYKVTLVTPEGKQELECPDDVYILDAAEEAGIDLPYSCRAGSCSSCAGKVTSGSVNQDDG -FER1_SPIOL/1-147 AYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQ -FER3_RAPSA/1-96 TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ -FER1_ARATH/1-148 TYKVKFITPEGELEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ -FER_BRANA/1-96 TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGFVDQSDE -FER2_ARATH/1-148 TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ -Q93Z60_ARATH/1-118 TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ -FER1_MAIZE/1-150 TYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ -O80429_MAIZE/1-140 TYNVKLITPEGEVELQVPDDVYILDFAEEEGIDLPFSCRAGSCSSCAGKVVSGSVDQSDQ -1A70|/1-97 AYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQ - -FER_CAPAA/1-97 NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG- -FER_CAPAN/1-144 NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG- -FER1_SOLLC/1-144 NFLDEDQEAAGFVLTCVAYPKGDVTIETHKEEELTA- -Q93XJ9_SOLTU/1-144 KFLDDDQEAAGFVLTCVAYPKCDVTIETHKEEELTA- -FER1_PEA/1-149 SFLDDEQIEAGFVLTCVAYPTSDVVIETHKEEDLTA- -Q7XA98_TRIPR/1-152 SFLDDEQIEGGWVLTCVAFPTSDVTIETHKEEELTA- -FER1_MESCR/1-148 SFLDDDQIKEGWVLTCVAYPTGDVTIETHKEEELTA- -FER1_SPIOL/1-147 SFLDDDQIDEGWVLTCAAYPVSDVTIETHKEEELTA- -FER3_RAPSA/1-96 SFLDDDQIAEGFVLTCAAYPTSDVTIETHREEDMV-- -FER1_ARATH/1-148 SFLDDEQIGEGFVLTCAAYPTSDVTIETHKEEDIV-- -FER_BRANA/1-96 SFLDDDQIAEGFVLTCAAYPTSDVTIETHKEEELV-- -FER2_ARATH/1-148 SFLDDEQMSEGYVLTCVAYPTSDVVIETHKEEAIM-- -Q93Z60_ARATH/1-118 SFLDD-------------------------------- -FER1_MAIZE/1-150 SYLDDGQIADGWVLTCHAYPTSDVVIETHKEEELTGA -O80429_MAIZE/1-140 SFLNDNQVADGWVLTCAAYPTSDVVIETHKEDDLL-- -1A70|/1-97 SFLDDDQIDEGWVLTCAAYPVSDVTIETHKKEELTA - +FER_CAPAA/1-97 -----------------------------------------------------------A 1 +FER_CAPAN/1-144 MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA 48 +FER1_SOLLC/1-144 MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48 +Q93XJ9_SOLTU/1-144 MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48 +FER1_PEA/1-149 MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA 53 + +FER_CAPAA/1-97 SYKVKLI 8 +FER_CAPAN/1-144 SYKVKLI 55 +FER1_SOLLC/1-144 SYKVKLI 55 +Q93XJ9_SOLTU/1-144 SYKVKLI 55 +FER1_PEA/1-149 SYKVKLV 60 diff --git a/help/help.jhm b/help/help.jhm index 010bca8..78f86b6 100755 --- a/help/help.jhm +++ b/help/help.jhm @@ -22,7 +22,7 @@ - + @@ -54,6 +54,7 @@ + @@ -69,8 +70,6 @@ - - diff --git a/help/helpTOC.xml b/help/helpTOC.xml index 20dd8db..77ddd88 100755 --- a/help/helpTOC.xml +++ b/help/helpTOC.xml @@ -24,6 +24,8 @@ + + @@ -58,8 +60,6 @@ - - @@ -76,8 +76,7 @@ - - + diff --git a/help/html/editing/index.html b/help/html/editing/index.html index fd8c5a3..b3600c9 100755 --- a/help/html/editing/index.html +++ b/help/html/editing/index.html @@ -61,7 +61,7 @@ Undo / redo - editing of sequences (insertion/removal of gaps, removal of sequences, trimming sequences etc) may be undone or redone at any time using the appropriate menu items from the edit - menu. The undo history list only allows a maximum of 10 actions. + menu.

Trimming alignment - First select a column by clicking the scale indicator (above the sequences) The alignment may then be diff --git a/help/html/features/chimera.html b/help/html/features/chimera.html index 68ac465..e1227de 100644 --- a/help/html/features/chimera.html +++ b/help/html/features/chimera.html @@ -211,41 +211,32 @@ structure in the alignment. The regions used to calculate the superposition will be highlighted using the 'Cartoon' rendering style, and the remaining data shown as a chain - trace.

+ trace.
+
-

  • EXPERIMENTAL FEATURES
    - - These are only available if the Tools→Enable - Experimental Features option is enabled. (Since Jalview 2.10.2) -
      -
    • Write Jalview features
      Selecting - this option will create new residue attributes for any - features currently visible in the associated alignment - views, allowing those positions to be selected and - analysed with via Chimera's 'Render by Attribute' tool - (found in the Tools submenu called Structure Analysis).
      -
      If you use this option, please remember to select - the Refresh Menus option in Chimera's Render by - Attribute dialog box in order to see the attributes - derived from Jalview sequence features. -

      - View - this function's issue in Jalview's bug tracker
    • -
    • Fetch Chimera Attributes
      This - submenu lists available Chimera residue attributes that - can be imported as Jalview features on associated - sequences.
      This is particularly useful for - transferring quantitative positional annotation. For - example, structure similarity for an alignment can be - visualised by transferring the local RMSD attributes - generated by Chimera's Match->Align tool onto aligned - sequences and displayed with a Graduated feature colour - scheme. -
      View - this function's issue in Jalview's bug tracker
    • -
  • -
  • Help
    +
  • Write Jalview + features
    Selecting this option will create + new residue attributes for any features currently visible in + the associated alignment views, allowing those positions to + be selected and analysed with via Chimera's 'Render by + Attribute' tool (found in the Tools submenu called Structure + Analysis).

    If you use this option, please + remember to select the Refresh Menus option in + Chimera's Render by Attribute dialog box in order to see the + attributes derived from Jalview sequence features. +
  • +
  • Fetch Chimera Attributes
    This + submenu lists available Chimera residue attributes that can + be imported as Jalview features on associated sequences.
    This + is particularly useful for transferring quantitative + positional annotation. For example, structure similarity for + an alignment can be visualised by transferring the local + RMSD attributes generated by Chimera's Match->Align tool + onto aligned sequences and displayed with a Graduated feature colour + scheme.
  • + +
  • Help
    • Chimera Help
      diff --git a/help/html/features/clarguments.html b/help/html/features/clarguments.html index e065494..fa273a5 100644 --- a/help/html/features/clarguments.html +++ b/help/html/features/clarguments.html @@ -122,30 +122,6 @@ -
      -dasserver nickname=URL
      - -
      - Add and enable a DAS server - with given nickname (alphanumeric or underscores only) for - retrieval of features for all alignments
      Sources that - also support the sequence command may be specified by - prepending the URL with 'sequence:'
      e.g. - sequence:http://localdas.somewhere.org/das/source -
      - - - - -
      -fetchfrom nickname
      - -
      - Query a DAS source called - nickname for features for the alignments and display them -
      - - - -
      -groovy FILE/URL
      Execute groovy script in FILE (where diff --git a/help/html/features/commandline.html b/help/html/features/commandline.html index 9cffc51..92d9323 100644 --- a/help/html/features/commandline.html +++ b/help/html/features/commandline.html @@ -49,11 +49,11 @@ provided by InstallAnywhere any output from the application will be sent to output.txt, not standard out.
      The Jalview application also requires a number of additional libraries on the - class path. The command line below adds the Jalview installation's - 'lib' directory to the list of directories that are searched for - jars to be added to the classpath: + class path. The command line below adds all the jar files in the + Jalview installation's 'lib' directory to the classpath, as well as + the Jalview application jar file:

      -
      java -Djava.ext.dirs=$INSTALL_DIR$/lib -cp $INSTALL_DIR$/jalview.jar jalview.bin.Jalview -open [FILE] 
      +
      java -classpath "$INSTALL_DIR$/lib/*:$INSTALL_DIR$/jalview.jar" jalview.bin.Jalview -open [FILE] 

      Use '-help' to get more information on the command line arguments that diff --git a/help/html/features/dasfeatures.html b/help/html/features/dasfeatures.html deleted file mode 100644 index 1965e70..0000000 --- a/help/html/features/dasfeatures.html +++ /dev/null @@ -1,77 +0,0 @@ - - - -DAS Features - - - -

      - DAS Sequence Feature Retrieval -

      -

      Jalview includes a client for retrieving sequences and their - features via the Distributed Annotation System.

      -
        -
      1. Open the Feature Settings panel by selecting "View - -> Feature Settings..."
      2. -
      3. Click on the "DAS - Settings" tabbed pane. -
      4. -
      5. Select the sources to use for DAS feature retrieval, then - click the "Fetch DAS Features" button. -
          -
        • Cancelling Feature Retrieval
          Press the Cancel - Fetch button to immediately stop feature retrieval. This - will not remove any features already added to the alignment, - but will halt any outstanding DAS requests.The cancel - fetch button is of particular use when one or more DAS - annotation servers are not responding! -
        -
      6. -
      -

      - If your DAS source selection contains sources which use UniProt - accession ids, you will be asked whether Jalview should find UniProt - Accession ids for the given sequence names. It is important to - realise that many DAS sources only use UniProt accession ids, rather - than Swissprot/UniProt sequence names.
      The database - reference fetcher documentation describes how Jalview discovers - what database references are appropriate for the sequences in the - alignment. -

        -
      • Note
        Please remember to save your - alignment if either the start/end numbering, or the sequence IDs - were updated during the ID retrieval process.
      • -
      -

        -

      - DAS support was introduced in Jalview Version 2.1. -

      -
      -

      - The DAS registry at http://www.dasregistry.org was - decommissioned early in 2015. An unmaintained mirror is currently - hosted at http://www.ebi.ac.uk/das-srv/registry/. -

      -

        - - diff --git a/help/html/features/dassettings.html b/help/html/features/dassettings.html deleted file mode 100644 index 9600070..0000000 --- a/help/html/features/dassettings.html +++ /dev/null @@ -1,74 +0,0 @@ - - - -DAS Settings - - - -

      - DAS Settings -

      -

      - Jalview can retrieve sequences and features from many DAS sources. The DAS sources - that it uses are discovered and selected via the DAS - settings panel, opened either from the View→Feature Settings dialog - box from the alignment window's menu bar, or the Tools→Preferences - dialog box opened from the Desktop menu bar. -

      -

      - -

      The available sources are listed in the table using each - source's Nickname as its identifier. Clicking on a source's entry in - the table reveals more information about that service in the panel - to the right. Select the tickbox in the "Use Source" - column for a source to add it to the set Jalview queries for - alignment and sequence features.

      -

      You can filter the visible DAS sources by authority, type and - "label". You should read the DAS documentation to - understand more about these values. -

      - Updating the list of sources -

      -

      - When the DAS Settings panel is first opened, and when the 'Refresh - source' buton is pressed, a list of DAS sources is retrieved from - the DAS registry URL. Note that the registry hosted at - http://www.dasregistry.org/das/ was retired at the start of 2015. An - alternative service is currently hosted at - http://www.ebi.ac.uk/das-srv/registry/das/. To connect to this - service, ensure your .jalview_properties file includes the following - line:
      DAS_REGISTRY_URL=http\://www.ebi.ac.uk/das-srv/registry/das/ -

      -

      - Adding your own DAS Sources -

      -

      You can add your own DAS source to the list by clicking the - "Add Local Source" button. Enter the URL and nickname of - your additional service. It should be noted that Jalview 2.1 will - not query additional sources for more information, but this will be - implemented in future editions. -

        - - diff --git a/help/html/features/featuresFormat.html b/help/html/features/featuresFormat.html index fd6b99f..4d13dcd 100755 --- a/help/html/features/featuresFormat.html +++ b/help/html/features/featuresFormat.html @@ -60,7 +60,10 @@ contains tab separated text fields. No comments are allowed.

      -

      The first set of lines contain type definitions: +

      + Feature Colours +

      +

      The first set of lines contain feature type definitions and their colours:

       Feature label	Feature Colour
       
      @@ -72,21 +75,37 @@
         
      • A single colour specified as either a red,green,blue 24 bit triplet in hexadecimal (eg. 00ff00) or as comma separated numbers - (ranging from 0 to 255))
      • + (ranging from 0 to 255))
        + (For help with colour values, see https://www.w3schools.com/colors/colors_converter.asp.)
      • A graduated colourscheme specified as a "|" separated list of fields:
        -[label|]<mincolor>|<maxcolor>|[absolute|]<minvalue>|<maxvalue>[|<thresholdtype>|[<threshold value>]]
        +[label or score or attribute|attName|]<mincolor>|<maxcolor>|[absolute|]<minvalue>|<maxvalue>[|<novalue>][|<thresholdtype>|[<threshold value>]]
         
        The fields are as follows:
          -
        • label
          Indicate that the feature +
        • label
          Indicates that the feature description should be used to create a colour for features of this type.
          Note: if no threshold value is - needed then the final '|' may be omitted.
          This + needed then only 'label' is required.
          This keyword was added in Jalview 2.6
        • +
        • score
          Indicates that the feature + score should be used to create a graduated colour for features of + this type, in conjunction with mincolor, maxcolor.
          This keyword was added in Jalview 2.11. + It may be omitted (score is the default) if mincolor and maxcolor are specified. +
        • + +
        • attribute|attName
          Indicates that the value of feature + attribute 'attName' should be used to create a colour for features of + this type. +
          For example, attribute|clinical_significance to colour by clinical_significance. +
          To colour by range of a numeric attribute, include mincolor and maxcolor, or omit to colour by text (category). +
          (Note: the value of the attribute used for colouring will also be shown in the tooltip as you mouse over features.) +
          A sub-attribute should be written as, for example, CSQ:IMPACT. +
          This keyword was added in Jalview 2.11
        • +
        • mincolor and maxcolor
          Colour triplets specified as hexadecimal or comma separated values (may be left blank for a label style colourscheme, @@ -99,10 +118,15 @@
        • minvalue and maxvalue
          Minimum and maximum values defining the range of scores for - which the colour range will be defined over. If minvalue is + which the colour range will be defined over.
          If minvalue is greater than maxvalue then the linear mapping will have negative gradient.
        • +
        • novalue
          + Specifies the colour to use if colouring by attribute, when the attribute is absent. + Valid options are novaluemin, novaluemax, novaluenone, to use mincolor, maxcolor, or no colour. +
          If not specified this will default to novaluemin.
        • +
        • thresholdtype
          Either "none", "below", or "above". below and above require an additional threshold @@ -113,12 +137,46 @@

        +

        + Feature Filters +

        +

        This section is optional, and allows one or more filters to be defined for each feature type. +
        Only features that satisfy the filter conditions will be displayed. +
        Begin with a line which is just STARTFILTERS, and end with a line which is just ENDFILTERS. +
        Each line has the format: +

        featureType <tab> (filtercondition1) [and|or] (filtercondition2) [and|or]...
        + The parentheses are not needed if there is only one condition. + Combine multiple conditions with either and or or (but not a mixture). +
        Each condition is written as: +
        Label or Score or AttributeName condition [value]
        + where either the label (description), (numeric) score, or (text or numeric) attribute is tested against the condition. +
        condition is not case sensitive, and should be one of +
          +
        • Contains - description (or attribute) should contain the given value (not case sensitive); example clinical_significance contains Pathogenic
        • +
        • NotContains - description (or attribute) should not contain the given value
        • +
        • Matches - description (or attribute) should match the given value (not case sensitive)
        • +
        • NotMatches - description (or attribute) should not match the given value (not case sensitive)
        • +
        • Present - attribute is present on the feature (no value required); example CSQ:SIFT present
        • +
        • NotPresent - attribute is not present on the feature (no value required)
        • +
        • EQ - feature score, or specified attribute, is equal to the (numeric) value
        • +
        • NE, LT, LE, GT, GE - tests for not equal to / less than / less than or equal to / greater than / greater than or equal to the value
        • +
        + A non-numeric value always fails a numeric test.
        If either attribute name, or value to compare, contains spaces, then enclose in single quotes: + 'mutagenesis site' contains 'decreased affinity' +
        Tip: some examples of filter syntax are given below; or to see more, first configure colours and filters in Jalview, then File | Export Features to Textbox in Jalview Format. +
        Feature filters were added in Jalview 2.11 +

        + +

        + Feature Instances +

        +

        The remaining lines in the file are the sequence annotation definitions, where the now defined features are attached to regions on particular sequences. Each feature can optionally include some descriptive text which is displayed in a tooltip when the mouse is - near the feature on that sequence (and can also be used to generate - a colour the feature).

        + near the feature on that sequence (and may also be used to generate + a colour for the feature).

        If your sequence annotation is already available in GFF2 (http://gmod.org/wiki/GFF2) or @@ -204,6 +262,13 @@ signal peptide 0,155,165 helix ff0000 strand 00ff00 coil cccccc +kdHydrophobicity ccffcc|333300|-3.9|4.5|above|-2.0 + +STARTFILTERS +metal ion-binding site Label Contains sulfur +kdHydrophobicity (Score LT 1.5) OR (Score GE 2.8) +ENDFILTERS + Your Own description here FER_CAPAA -1 3 93 domain Your Own description here FER_CAPAN -1 48 144 chain Your Own description here FER_CAPAN -1 50 140 domain @@ -211,10 +276,16 @@ Your Own description here FER_CAPAN -1 136 136 modified residue Your Own description here FER1_LYCES -1 1 47 transit peptide Your Own description here Q93XJ9_SOLTU -1 1 48 signal peptide Your Own description here Q93XJ9_SOLTU -1 49 144 chain -startgroup secondarystucture + +STARTGROUP secondarystucture PDB secondary structure annotation FER1_SPIOL -1 52 59 strand PDB secondary structure annotation FER1_SPIOL -1 74 80 helix -endgroup secondarystructure +ENDGROUP secondarystructure + +STARTGROUP kd +Hydrophobicity score by kD Q93XJ9_SOLTU -1 48 48 kdHydrophobicity 1.8 +ENDGROUP kd + GFF FER_CAPAA GffGroup domain 3 93 . .

      diff --git a/help/html/features/jmol.html b/help/html/features/jmol.html index 0cd6168..ac2489b 100644 --- a/help/html/features/jmol.html +++ b/help/html/features/jmol.html @@ -61,16 +61,10 @@

      -->

      Superposing structures based on - their aligned sequences
      If several structures are - available on the alignment, you may add additional structures to an - existing Jmol view by selecting their entry in the appropriate - pop-up menu. Jalview will ask you if you wish to add the structure - to the existing alignment, and if you do, it will import and - superimpose the new PDB file using the corresponding positions from - the alignment. If the alignment is subsequently edited, you can use - the Jmol→Align menu option from - the menu bar of the structure view window to superpose the - structures using the updated alignment.
      Sequence + their aligned sequences

      If several structures are shown + in a view, you can superimpose them using the corresponding + positions from the alignment via the Jmol→Align + menu option from the menu bar of the structure view window.
      Sequence based structure superposition was added in Jalview 2.6

      diff --git a/help/html/features/preferences.html b/help/html/features/preferences.html index b29b66b..52e88db 100755 --- a/help/html/features/preferences.html +++ b/help/html/features/preferences.html @@ -65,10 +65,6 @@ Preferences tab contains settings affecting the export of sequence alignments and EPS files.

    • -
    • The "DAS - Settings" Preferences tab allows you to select which DAS - sources to use when fetching DAS Features. -
    • The "Web Service" Preferences tab allows you to configure the JABAWS @@ -367,7 +363,7 @@ and PDB file association (if available). The Jalview id/start-end option is ignored if Modeller output is selected.

      - e"Editinge" Preferences tab + "Editing" Preferences tab

      There are currently three options available which can be selected / deselected.

      diff --git a/help/html/features/schooser_enter-id.png b/help/html/features/schooser_enter-id.png index f551c50..4af0d53 100644 Binary files a/help/html/features/schooser_enter-id.png and b/help/html/features/schooser_enter-id.png differ diff --git a/help/html/features/schooser_main.png b/help/html/features/schooser_main.png index ab69427..ca793fd 100644 Binary files a/help/html/features/schooser_main.png and b/help/html/features/schooser_main.png differ diff --git a/help/html/features/search.html b/help/html/features/search.html index 72e5bdf..eec68ee 100755 --- a/help/html/features/search.html +++ b/help/html/features/search.html @@ -159,7 +159,7 @@ td {

      The search history keeps up to 99 queries by default. To clear the history, or modify the size of the history, right-click the text box.

      - +

      Other dialogs that provide a query history

      diff --git a/help/html/features/searchclearhist.png b/help/html/features/searchclearhist.png index 200c4d3..29cc34f 100644 Binary files a/help/html/features/searchclearhist.png and b/help/html/features/searchclearhist.png differ diff --git a/help/html/features/seqfeatures.html b/help/html/features/seqfeatures.html index 3c9d2b8..eeb63f6 100755 --- a/help/html/features/seqfeatures.html +++ b/help/html/features/seqfeatures.html @@ -87,10 +87,7 @@ href="featuresettings.html">Sequence Feature Settings dialog box. Feature colour schemes and display parameters are unique to a particular alignment, so it is possible to colour the same - sequence features differently in different alignment views.
      - Since Jalview 2.1, it is possible to add DAS - features to an alignment via the DAS tabbed pane of the feature - settings window. + sequence features differently in different alignment views.

      View→Sequence ID Tooltip→Show diff --git a/help/html/features/seqfetch.html b/help/html/features/seqfetch.html index 44aa1c2..e726c49 100755 --- a/help/html/features/seqfetch.html +++ b/help/html/features/seqfetch.html @@ -26,19 +26,13 @@

      Sequence Fetcher

      -

      - Jalview can retrieve sequences from certain databases using either - the DBFetch service provided by the EMBL European Bioinformatics - Institute, or, since Jalview 2.4, DAS servers capable of the sequence - command (configured in DAS settings). -

      -

      The Sequence Fetcher can be opened via the "File" +

      Jalview can retrieve sequences from a range of sequence, 3D + structure, genomic and domain family databases provided by EMBL-EBI.

      +

      The Sequence Fetcher can be opened via the "File" menu on the main desktop in order to retrieve sequences as a new alignment, or opened via the "File" menu of an existing alignment to import additional sequences. There may be a short delay - when the sequence fetcher is first opened, whilst Jalview compiles - the list of available sequence datasources from the currently - defined DAS server registry.

      + when the sequence fetcher is first opened, whilst Jalview contacts each database's web API.

      Every time a new fetcher is opened, you will need to select the database you want to retrieve sequences from the database @@ -51,11 +45,6 @@ tooltips are shown if you mouse over some sources, explaining what the database will retrieve. You can select one by using the up/down arrow keys and hitting return, or by double clicking with the mouse. -
      - If you have DAS sources enabled, then you may have several - sources for the same type of sequence identifier, and these will - be grouped together in a sub-branch branch labeled with the - identifier.

      Once you have selected a sequence database, its fetcher dialog will open. Jalview provides two types of dialog:

      @@ -82,17 +71,6 @@ currently selected database into the retrieval box. Finally, press "OK" to initiate the retrieval.
    • -

      - Only retrieving part of a sequence -

      -

      - When using DAS sources (indicated by a "(DAS)"), - you can append a range in addition to a sequence ID. For example, to - retrieve 50 residues starting at position 35 in UNIPROT sequence - P73137 using the UNIPROT DAS server, you would enter - "'P73137:35,84'.
      Full support for DAS range - queries was introduced in Jalview 2.8 -

      If you use the WSDBFetch sequence fetcher services (EMBL, UniProt, PFAM, and RFAM) in work for publication, please cite:

      diff --git a/help/html/features/splitView.html b/help/html/features/splitView.html index be1bd66..e1c07c1 100644 --- a/help/html/features/splitView.html +++ b/help/html/features/splitView.html @@ -76,7 +76,7 @@ or "View→Nucleotide" (in the protein panel) allows you to show or hide one or other of the linked alignment panels. -
    • Panel heights are adjusted dragging the divider between +
    • Panel heights are adjusted by dragging the divider between them using the mouse
    • "View→New View / Expand Views / Gather Views" behave as for a normal diff --git a/help/html/features/structurechooser.html b/help/html/features/structurechooser.html index fc71826..785c429 100644 --- a/help/html/features/structurechooser.html +++ b/help/html/features/structurechooser.html @@ -25,14 +25,14 @@

      - Structure Chooser + Structure Chooser Dialog Box

      - The Structure Chooser interface allows you to interactively select - which PDB structures to view for the currently selected set of + The Structure Chooser allows you to select + 3D structures to view for the currently selected set of sequences. It is opened by selecting the "3D - Structure Data.." option from the Sequence ID panel's option from the Sequence ID panel's pop-up menu. The dialog provides:

      @@ -49,11 +49,16 @@

      Selecting and Viewing Structures

      +

      The drop-down menu offers different options for structure + discovery; the 'Cached' view is shown automatically if existing + structure data has been imported for the selected sequences, and if + none is available, the import PDB/mmCIF file options are shown.

      Once one or more structures have been selected, pressing the View - button will import them into Add button will import them a new or existing - structure view. + structure view. When multiple views are available, use the + drop-down menu to pick the target viewer for the structures.

      Automated discovery of structure data @@ -89,17 +94,16 @@ criteria (e.g. worst quality rather than best).

      - + -
      The screenshot above shows the Structure Chooser interface - along with the meta-data of auto-discovered structures for the - sample alignment. If no structures were - auto-discovered, options for manually associating PDB records will be shown (see below). -

      +
      The screenshot above shows the Structure Chooser displayed after + selecting all the sequences in the Jalview example project. If no + structures were auto-discovered, options for manually associating + PDB records will be shown (see below).

      Exploration of meta-data for available structures

      Information on each structure available is displayed in columns @@ -109,7 +113,7 @@ Columns' tab and tick the columns which you want to see.

      + style="width: 464px; height: 173px;">
      Manual selection/association of PDB files with Sequences diff --git a/help/html/features/viewingpdbs.html b/help/html/features/viewingpdbs.html index 45d979f..b1ad4ba 100755 --- a/help/html/features/viewingpdbs.html +++ b/help/html/features/viewingpdbs.html @@ -82,6 +82,7 @@ provided it is installed and can be launched by Jalview. The default viewer can be configured in the Structure tab in the Tools→Preferences dialog box. +

      Structure data imported into Jalview can also be processed to display secondary structure and temperature factor annotation. See @@ -89,44 +90,30 @@ for more information.

      - After pressing the - 'View' button in the Structure Chooser
      The behaviour of - the 'View' button depends on the number of structures selected, and - whether structure views already exist for the selected structures or - aligned sequences. +
      Controlling where the new structures + will be shown +
      The Structure Chooser offers several options + for viewing a structure.
      New View will open a new + structure viewer for the selected structures, but if there are views + already open, you can select which one to use, and press the Add + button. Jalview can automatically superimpose new structures based + on the linked alignments - but if this is not desirable, simple + un-tick the Superpose Structures checkbox. +

      -

      If multiple structures are selected, then Jalview will always - create a new structure view. The selected structures will be - imported into this view, and superposed with the matched positions - from the aligned sequences. A message in the structure viewer's - status bar will be shown if not enough aligned columns were - available to perform a superposition.

      - If a single PDB structure is selected, one of the - following will happen: + Superposing structures
      Jalview superposes structures using + the visible portions of any associated sequence alignments. A + message in the structure viewer's status bar will be shown if not + enough aligned columns were available to perform a superposition.

      - -
        -
      • If no structures are open, then an interactive display of - the structure will be opened in a new window.
      • - -
      • If another structure is already shown for the current - alignment, then you will be asked if you want to add and to the structure in the existing view. - (new feature in Jalview 2.6). -
      • - -
      • If the structure is already shown, then you will be - prompted to associate the sequence with an existing view of the - selected structure. This is useful when working with multi-domain - or multi-chain PDB files.
      • - -
      • See the Jmol - and Chimera PDB viewer help pages for - more information about the display. -
      • -
      - +

      + See the Jmol + and Chimera help pages for + more information about their capabilities.

      +

      Retrieving sequences from the PDB
      You can diff --git a/help/html/memory.html b/help/html/memory.html index 2142f98..9437a60 100755 --- a/help/html/memory.html +++ b/help/html/memory.html @@ -115,7 +115,7 @@ lax.nl.java.option.java.heap.size.initial=500m ! <string>-Xms2M</string> ! <string>-Xmx64M</string> </array> - Exchange the above two string tags for :

      +
      Exchange the above two string tags for :
       <string>-Xms500M</string>
       <string>-Xmx1000M</string>
       
      @@ -125,6 +125,11 @@ lax.nl.java.option.java.heap.size.initial=500m the file and try to start Jalview in the normal way. If it doesn't start, see below...
    +

    + Please Note: We do modify the default memory settings in + Jalview from time to time, so you may find different numbers to + those shown in the examples above. +

    Jalview doesn't start... What do the memory settings mean ? @@ -140,6 +145,12 @@ lax.nl.java.option.java.heap.size.initial=500m enlighten us if you know better!). Our experiments found 1000m to be the biggest setting that could be used on a 1GB machine. Just try reducing the sizes until Jalview starts up properly!

    +

    + We increased the default memory in Jalview 2.10.5 to 1G. To launch + Jalview with the pre 2.10.5 default memory allocation, use the Jalview + 256MB JNLP. +

     

    diff --git a/help/html/menus/alignmentMenu.html b/help/html/menus/alignmentMenu.html index 00a2ec4..f3ab75d 100755 --- a/help/html/menus/alignmentMenu.html +++ b/help/html/menus/alignmentMenu.html @@ -314,9 +314,7 @@ href="../features/featuresettings.html">Sequence Feature Settings...

    Opens the Sequence Feature Settings dialog box to control the colour - and display of sequence features on the alignment, and - configure and retrieve features from DAS annotation - servers.
  • + and display of sequence features on the alignment.
  • Sequence ID Tooltip (application only)
    This submenu's options allow the inclusion or exclusion of non-positional sequence features @@ -579,28 +577,26 @@ is dynamic, and may contain user-defined web service entries in addition to any of the following ones:
      -
    • Fetch DB References
      This - submenu contains options for accessing any of the database - services that Jalview is aware of (e.g. DAS sequence servers - and the WSDBFetch service provided by the EBI) to verify - sequence start/end positions and retrieve all database cross - references and PDB ids associated with all or just the - selected sequences in the alignment. -
        -
      • 'Trim Retrieved Sequences' - when checked, Jalview - will discard any additional sequence data for accessions - associated with sequences in the alignment.
        Note: - Disabling this could cause out of memory errors when - working with genomic sequence records !
        Added - in Jalview 2.8.1 -
      • -
      • 'Standard Databases' will check sequences against - the EBI databases plus any active DAS sequence sources
      • -
      Other sub-menus allow you to pick a specific source to query - - sources are listed alphabetically according to their - nickname. -

    • -
    +
  • Fetch DB References
    This + submenu contains options for accessing any of the database + services that Jalview is aware of (e.g. those provided by + EMBL-EBI) to verify sequence start/end positions and retrieve all + database cross references and PDB ids associated with all or just + the selected sequences in the alignment. +
      +
    • 'Trim Retrieved Sequences' - when checked, Jalview will + discard any additional sequence data for accessions associated + with sequences in the alignment.
      Note: + Disabling this could cause out of memory errors when working + with genomic sequence records !
      Added + in Jalview 2.8.1 +
    • +
    • 'Standard Databases' will check sequences against the + EBI databases.
    • +
    Other sub-menus allow you to pick a specific source to query - + sources are listed alphabetically according to their nickname. +

  • +

    Selecting items from the following submenus will start a remote service on compute facilities at the University of Dundee, or elsewhere. You need a continuous network connection in order to diff --git a/help/html/menus/alwview.html b/help/html/menus/alwview.html index 2028304..33ec77f 100755 --- a/help/html/menus/alwview.html +++ b/help/html/menus/alwview.html @@ -53,8 +53,7 @@

  • Sequence Feature Settings...
    Opens the Sequence Feature Settings dialog box to control the colour and display of - sequence features on the alignment, and configure and retrieve - features from DAS annotation servers.
  • + sequence features on the alignment.
  • Sequence ID Tooltip (application only)
    This submenu's options allow the inclusion or exclusion of non-positional sequence features or database cross diff --git a/help/html/menus/desktopMenu.html b/help/html/menus/desktopMenu.html index a93ce4b..d716e33 100755 --- a/help/html/menus/desktopMenu.html +++ b/help/html/menus/desktopMenu.html @@ -86,7 +86,7 @@ the Groovy Console for interactive scripting.

  • -
  • Enable Experimental Features Enable or disable features still under development in Jalview's user interface. This setting is remembered in your preferences. +
  • Vamsas For more details, read the diff --git a/help/html/menus/wsmenu.html b/help/html/menus/wsmenu.html index b71bc1a..b8b357e 100755 --- a/help/html/menus/wsmenu.html +++ b/help/html/menus/wsmenu.html @@ -29,14 +29,13 @@ dynamic, and may contain user-defined web service entries in addition to any of the following ones:
      -
    • Fetch DB References
      This - submenu contains options for accessing any of the database - services that Jalview is aware of (e.g. DAS sequence servers and - the WSDBFetch service provided by the EBI) to verify sequence - start/end positions and retrieve all database cross references - and PDB ids associated with all or just the selected sequences - in the alignment. -
        +
      • Fetch DB References
        This submenu + contains options for accessing any of the database services that + Jalview is aware of (e.g services provided by the EBI) to verify + sequence start/end positions and retrieve all database cross + references and PDB ids associated with all or just the selected + sequences in the alignment. +
        • 'Retrieve full Sequence' - when checked, Jalview will retrieve the full sequence for any accessions associated with sequences in the alignment.
          Note: @@ -45,7 +44,7 @@ in Jalview 2.8.1
        • 'Standard Databases' will check sequences against the - EBI databases plus any active DAS sequence sources<
        • + EBI databases
        Other submenus allow you to pick a specific source to query - sources are listed alphabetically according to their nickname.
      • diff --git a/help/html/releases.html b/help/html/releases.html index a36e31a..af417a7 100755 --- a/help/html/releases.html +++ b/help/html/releases.html @@ -67,23 +67,381 @@ li:before { + + +
        + 2.11.0
        + 8/09/2018
        +
        + +
        + +
          +
        • + Jalview doesn't hang when closing windows or the overview updates with large alignments. +
        • +
        +
        +
        + +
          +
        • + DAS sequence retrieval and annotation capabilities removed from the Jalview Desktop +
        • +
        +
        + + + +
        + 2.10.5
        10/09/2018
        +
        + +
        + +
          +
        • + Default memory for Jalview webstart and + InstallAnywhere increased to 1G. +
        • +
        • + Hidden sequence markers and representative + sequence bolding included when exporting alignment as EPS, + SVG, PNG or HTML. Display is configured via the + Format menu, or for command-line use via a jalview + properties file. +
        • +
        • + Ensembl client updated to Version 7 REST + API and sequence data now imported as JSON. +
        • +
        • + Change in recommended way of starting + Jalview via a Java command line: add jars in lib directory + to CLASSPATH, rather than via the deprecated java.ext.dirs + property. +
        • +
        + Development +
          +
        • + Support added to execute test suite + instrumented with Open + Clover +
        • +
        +
        +
        + +
          +
        • + Poorly scaled bar in quality annotation + row shown in Feredoxin Structure alignment view of example + alignment. +
        • +
        • + Annotation obscures sequences if lots of + annotation displayed. +
        • +
        • + Group conservation/consensus not shown + for newly created group when 'Apply to all groups' + selected +
        • +
        • + Corrupted display when switching to + wrapped mode when sequence panel's vertical scrollbar is + visible. +
        • +
        • + Alignment is black in exported EPS file + when sequences are selected in exported view. +
        • +
        • + Groups with different coloured borders + aren't rendered with correct colour. +
        • +
        • + Jalview could hang when importing certain + types of knotted RNA secondary structure. +
        • +
        • + Sequence highlight and selection in + trimmed VARNA 2D structure is incorrect for sequences that + do not start at 1. +
        • +
        • + '.' inserted into RNA secondary structure + annotation when columns are inserted into an alignment, + and when exporting as Stockholm flatfile. +
        • +
        • + Jalview annotation rows containing upper + and lower-case 'E' and 'H' do not automatically get + treated as RNA secondary structure. +
        • +
        • + .jvp should be used as default extension + (not .jar) when saving a jalview project file. +
        • +
        • + Mac Users: closing a window correctly + transfers focus to previous window on OSX +
        • +
        + Java 10 Issues Resolved +
          +
        • + OSX - Can't save new files via the File + or export menus by typing in a name into the Save dialog + box. +
        • +
        • + Jalview now uses patched version + of the VAqua5 + 'look and feel' which has improved compatibility with the + latest version of OSX. +
        • +
        +
        + + - +
        - 2.10.3b1
        5/12/2017
        + 2.10.4b1
        + 7/06/2018
        +
          +
        • + Use HGVS nomenclature for variant + annotation retrieved from Uniprot +
        • +
        • + Windows File Shortcuts can be dragged + onto the Jalview Desktop +
        • +
        +
        +
        + +
          +
        • + Cannot import features with multiple + variant elements (blocks import of some Uniprot records) +
        • +
        • + Clustal files with sequence positions in + right-hand column parsed correctly +
        • +
        • + Wrap view - export to SVG - IDs shown but + not alignment area in exported graphic +
        • +
        • + F2/Keyboard mode edits work when Overview + window has input focus +
        • +
        • + Annotation panel set too high when + annotation added to view (Windows) +
        • +
        • + Jalview Desktop is slow to start up when + network connectivity is poor +
        • +
        • + Drag URL from chrome, firefox, IE to + Jalview desktop on Windows doesn't open file
          Dragging + the currently open URL and links from a page viewed in + Firefox or Chrome on Windows is now fully supported. If + you are using Edge, only links in the page can be + dragged, and with Internet Explorer, only the currently + open URL in the browser can be dropped onto Jalview. +
        • +
        +
        + + + +
        + 2.10.4
        10/05/2018
        +
        + +
        + +
          +
        • + New Structure Chooser control + for disabling automatic superposition of multiple + structures and open structures in existing views +
        • +
        • + Mouse cursor changes to indicate Sequence + ID and annotation area margins can be click-dragged to + adjust them. +
        • +
        • + Jalview uses HTTPS for Uniprot, Xfam and + Ensembl services +
        • +
        • + Improved performance for large alignments + and lots of hidden columns +
        • +
        • + Improved performance when rendering lots + of features (particularly when transparency is disabled) +
        • +
        +
          +
        • + Structure and Overview aren't updated + when Colour By Annotation threshold slider is adjusted +
        • +
        • + Slow redraw when Overview panel shown + overlapping alignment panel +
        • +
        • + Overview doesn't show end of unpadded + sequence as gaps +
        • +
        • + Cross-reference handling + improved: CDS not handled correctly if transcript has no + UTR +
        • +
        • + Secondary structure and temperature + factor annotation not added to sequence when local PDB + file associated with it by drag'n'drop or structure + chooser +
        • +
        • + Answering 'No' to PDB Autoassociate + dialog doesn't import PDB files dropped on an alignment +
        • +
        • + Linked scrolling via protein horizontal + scroll bar doesn't work for some CDS/Protein views +
        • +
        • + Trackpad scrolling is broken on OSX on + Java 1.8u153 onwards and Java 1.9u4+. +
        • +
        • + Tooltip shouldn't be displayed for empty + columns in annotation row +
        • +
        • + Preferences panel's ID Width control is not + honored in batch mode +
        • +
        • + Linked sequence highlighting doesn't work + for structures added to existing Jmol view +
        • +
        • + 'View Mappings' includes duplicate + entries after importing project with multiple views +
        • +
        • + Viewing or annotating Uniprot + protein sequences via SIFTS from associated PDB entries + with negative residue numbers or missing residues fails +
        • +
        • + Exception when shading sequence with negative + Temperature Factor values from annotated PDB files (e.g. + as generated by CONSURF) +
        • +
        • + Uniprot 'sequence variant' features + tooltip doesn't include a text description of mutation +
        • +
        • + Invert displayed features very slow when + structure and/or overview windows are also shown +
        • +
        • + Selecting columns from highlighted regions + very slow for alignments with large numbers of sequences +
        • +
        • + Copy Consensus fails for group consensus + with 'StringIndexOutOfBounds' +
        • +
        • + VAqua(4) provided as fallback Look and Feel for OSX + platforms running Java 10 +
        • +
        • + Adding a structure to existing structure + view appears to do nothing because the view is hidden behind the alignment view +
        • +
        + Applet +
          +
        • + Copy consensus sequence option in applet + should copy the group consensus when popup is opened on it +
        • +
        + Batch Mode +
          +
        • + Fixed ID width preference is not respected +
        • +
        + New Known Defects +
          +
        • + Exceptions occasionally raised when + editing a large alignment and overview is displayed +
        • +
        • + 'Overview updating' progress bar is shown + repeatedly after a series of edits even when the overview + is no longer reflecting updates +
        • +
        • + 'SIFTS Mapping Error' when viewing + structures for protein subsequence (if 'Trim Retrieved + Sequences' enabled) or Ensembl isoforms (Workaround in + 2.10.4 is to fail back to N&W mapping) +
        • +
        +
        + + + + +
        + 2.10.3b1
        24/1/2018
        +
        + +
        +
        • Updated Certum Codesigning Certificate + (Valid till 30th November 2018)
        +
        + Desktop
          +
            +
          • Only one structure is loaded when several sequences and structures are selected for viewing/superposing
          • Alignment doesn't appear to scroll vertically via trackpad and scrollwheel
          • Jalview hangs if up/down arrows pressed in cursor mode when cursor lies in hidden region at start of alignment
          • Helix annotation has 'notches' when scrolled into view if columns are hidden
          • Annotation column filter can be slow to reset (ie after hitting cancel) for large numbers of hidden columns
          • -
          • User preference for disabling inclusion of sequence limits when exporting as flat file has no effect
          • -
              +
            • User preference for disabling inclusion of sequence limits when exporting as flat file has no effect
            • +
            • Reproducible cross-reference relationships when retrieving sequences from EnsemblGenomes
            • +
            +
        diff --git a/help/html/webServices/dbreffetcher.html b/help/html/webServices/dbreffetcher.html index 83c80ba..1f61dea 100644 --- a/help/html/webServices/dbreffetcher.html +++ b/help/html/webServices/dbreffetcher.html @@ -30,8 +30,7 @@ ID, and can be viewed in full via the Sequence Details window. . Jalview also uses references for the retrieval of - PDB structures and DAS features, and for + PDB structures, and for retrieving sequence cross-references such as the protein products of a DNA sequence.

        @@ -40,21 +39,18 @@ application provides three ways to access the retrieval function. Either:
          -
        • select the Discover PDB IDs option from the - structure submenu of the sequence's popup menu -
        • -
        • Choose one of the options from the 'Fetch DB Refs' submenu in +
        • select the Structure Chooser... option from + the Sequence ID popup menu. +
        • +
        • Choose one of the options from the 'Fetch DB Refs' submenu in the alignment window's Web Services menu:
            -
          • Standard Databases will fetch references from - the EBI databases plus currently selected DAS sources
          • -
          • The other entries submenus leading to lists of individual +
          • Standard Databases will fetch references from EBI + databases appropriate for the sequence type (Nucleotide or Protein)
          • +
          • The other entries submenus leading to lists of individual database sources that Jalview can access.
        • -
        • Answer 'Yes' when asked if you wish to retrieve database - references for your sequences after initiating a DAS Sequence - Feature fetch.

        Jalview discovers references for a sequence by generating a set of ID queries from the ID string of each sequence in the alignment. It diff --git a/help/html/webServices/index.html b/help/html/webServices/index.html index d0b497c..8f36013 100755 --- a/help/html/webServices/index.html +++ b/help/html/webServices/index.html @@ -36,15 +36,11 @@ Institute (EBI) and Distributed Annotation System servers that are capable of serving sequences. -

      • The DAS Feature - Fetcher enables the retrieval and visualization of features from - DAS annotation sources -
      • -
      • The Database Reference - Fetcher transfers database references from records available - from DAS or the public sequence databases. -
      • -
      • The Web Services menu in each alignment +
      • The Database Reference + Fetcher transfers database references and annotation from the public + sequence databases. +
      • +
      • The Web Services menu in each alignment window also provides access to the following:
        • Programs for multiple diff --git a/help/html/whatsNew.html b/help/html/whatsNew.html index 4bf1cec..13fe656 100755 --- a/help/html/whatsNew.html +++ b/help/html/whatsNew.html @@ -24,42 +24,50 @@

          - What's new in Jalview 2.10.3 ? -

          -

          - Version 2.10.3 was released in November 2017. The major focus was to - improve Jalview's sequence features datamodel and the scalability of - the alignment rendering system. The full list of bug fixes and new - features can be found in the 2.10.3 - Release Notes. Key improvements include: + What's new in Jalview 2.10.5 ?

          +

          Jalview 2.10.5 is a minor release that includes critical + patches for users working with Ensembl, RNA secondary structure + annotation, and those running Jalview on OSX with Java 10.

            -
          • Faster and more responsive UI when importing and working - with wide alignments and handling hundreds and thousands of - sequence features
          • -
          • Improved usability with PDB and UniProt Free Text - Search dialog, and new tab for retrieval of sequences for lists of - IDs. +
          • Jalview's default memory limit increased to 1G.
            If you have + problems starting Jalview 2.10.5 and you have 1G or less + physical memory on your machine, you will need to reduce the memory allocated to + Jalview. +
          • +
          • EPS, PNG and SVG export now includes hidden sequence + markers, and representative sequences are marked in bold.
          • +
          • Ensembl Client updated for Ensembl Rest API v7.
            The + latest Ensembl API is not backwards compatible with earlier + versions of Jalview, so if you require Ensembl functionality you + will need to install this release.
          • -
          • Short names assigned to sequences retrieved from UniProt
          • -
          • Groovy console upgraded to 2.4.12 (improved support for Java 9)
          • +
          • Improved support for VIENNA extended dot-bracket notation + for RNA secondary structure.
          • +
          • Positional and selected region highlighting in VARNA + 'trimmed sequence' view made more reliable.

          - Experimental Features + The full list of bugs fixed in this release can be found in the 2.10.5 Release Notes. The + majority of bug fixes and improvements in 2.10.5 are due to Jalview users + contacting us via the jalview-discuss email list. Thanks to everyone + who took the time to help make Jalview better !

          - Remember, please enable the Experimental Features option in - the Jalview Desktop's Tools menu, and then restart Jalview - if you want to try out features below: + Jalview and Java 10

          +

          This release addresses a critical bug for OSX users who are + running Jalview with Java 10 which can prevent files being saved + correctly through the 'Save As' dialog box.

          + Known Issues
            -
          • Annotation transfer between Chimera and Jalview
            Two - new entries in - the Chimera viewer's Chimera menu allow positional annotation to - be exchanged between Chimera and Jalview.
          • +
          • OSX: The 'Open File' dialog for Jalview's Groovy Console + appears with the title 'Save As', and attempting to select a file to load yields a FileNotFound exception.
            The workaround is to first clear the 'Untitled' filename before selecting the file you wish to load. +
          • +
          • OSX: Links don't open when clicked on or via the Sequence or Alignment window popup menu.
          • +
          • OSX (Webstart): Jalview only displays old news feed items
          - diff --git a/lib/VAqua5-patch.jar b/lib/VAqua5-patch.jar new file mode 100644 index 0000000..7b5c27b Binary files /dev/null and b/lib/VAqua5-patch.jar differ diff --git a/lib/htsjdk-1.133.jar b/lib/htsjdk-1.133.jar deleted file mode 100644 index f084258..0000000 Binary files a/lib/htsjdk-1.133.jar and /dev/null differ diff --git a/lib/htsjdk-2.12.0.jar b/lib/htsjdk-2.12.0.jar new file mode 100644 index 0000000..1df12b2 Binary files /dev/null and b/lib/htsjdk-2.12.0.jar differ diff --git a/lib/jdas-1.0.4.jar b/lib/jdas-1.0.4.jar deleted file mode 100644 index fb5d128..0000000 Binary files a/lib/jdas-1.0.4.jar and /dev/null differ diff --git a/nbbuild.xml b/nbbuild.xml index d6e4cf3..ca8a275 100644 --- a/nbbuild.xml +++ b/nbbuild.xml @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. --> - diff --git a/resources/authors.props b/resources/authors.props index 3488ac6..3c06708 100644 --- a/resources/authors.props +++ b/resources/authors.props @@ -1,4 +1,4 @@ -YEAR=2016 -AUTHORS=J Procter, M Carstairs, TC Ofoegbu, K Mourao, AM Waterhouse, J Engelhardt, LM Lui, A Menard, D Barton, N Sherstnev, D Roldan-Martinez, M Clamp, S Searle, G Barton -AUTHORFNAMES=Jim Procter, Mungo Carstairs, Tochukwu 'Charles' Ofoegbu, Kira Mourao, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Anne Menard, Daniel Barton, Natasha Sherstnev, David Roldan-Martinez, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton +YEAR=2018 +AUTHORS=J Procter, M Carstairs, B Soares, K Mourao, TC Ofoegbu, AM Waterhouse, J Engelhardt, LM Lui, A Menard, D Barton, N Sherstnev, D Roldan-Martinez, M Clamp, S Searle, G Barton +AUTHORFNAMES=Jim Procter, Mungo Carstairs, Ben Soares, Kira Mourao, Tochukwu 'Charles' Ofoegbu, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Anne Menard, Daniel Barton, Natasha Sherstnev, David Roldan-Martinez, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton \ No newline at end of file diff --git a/resources/images/idwidth.gif b/resources/images/idwidth.gif deleted file mode 100755 index c1bd8cb..0000000 Binary files a/resources/images/idwidth.gif and /dev/null differ diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 94f7eff..ae5b0e7 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -242,7 +242,6 @@ label.documentation = Documentation label.about = About... label.show_sequence_limits = Show Sequence Limits action.feature_settings = Feature Settings... -label.feature_settings = Feature Settings label.all_columns = All Columns label.all_sequences = All Sequences label.selected_columns = Selected Columns @@ -267,6 +266,7 @@ label.use_rnaview = Use RNAView for secondary structure label.autoadd_secstr = Add secondary structure annotation to alignment label.autoadd_temp = Add Temperature Factor annotation to alignment label.structure_viewer = Default structure viewer +label.double_click_to_browse = Double-click to browse for file label.chimera_path = Path to Chimera program label.chimera_path_tip = Jalview will first try any path entered here, else standard installation locations.
          Double-click to browse for file. label.invalid_chimera_path = Chimera path not found or not executable @@ -274,6 +274,7 @@ label.chimera_missing = Chimera structure viewer not found.
          Please enter the label.chimera_failed = Error opening Chimera - is it installed?\nCheck path in Preferences, Structure label.min_colour = Minimum Colour label.max_colour = Maximum Colour +label.no_colour = No Colour label.use_original_colours = Use Original Colours label.threshold_minmax = Threshold is min/max label.represent_group_with = Represent Group with {0} @@ -281,9 +282,9 @@ label.selection = Selection label.group_colour = Group Colour label.sequence = Sequence label.view_pdb_structure = View PDB Structure -label.min = Min: -label.max = Max: -label.colour_by_label = Colour by label +label.min_value = Min value +label.max_value = Max value +label.no_value = No value label.new_feature = New Feature label.match_case = Match Case label.view_alignment_editor = View in alignment editor @@ -368,6 +369,8 @@ label.optimise_order = Optimise Order label.seq_sort_by_score = Sequence sort by Score label.load_colours = Load Colours label.save_colours = Save Colours +label.load_colours_tooltip = Load feature colours and filters from file +label.save_colours_tooltip = Save feature colours and filters to file label.fetch_das_features = Fetch DAS Features label.selected_database_to_fetch_from = Selected {0} database {1} to fetch from {2} label.database_param = Database: {0} @@ -400,10 +403,6 @@ label.view_name_original = Original label.enter_view_name = Enter View Name label.enter_label = Enter label label.enter_label_for_the_structure = Enter a label for the structure -label.pdb_entry_is_already_displayed = {0} is already displayed.\nDo you want to re-use this viewer ? -label.map_sequences_to_visible_window = Map Sequences to Visible Window: {0} -label.add_pdbentry_to_view = Do you want to add {0} to the view called\n{1}\n -label.align_to_existing_structure_view = Align to existing structure view label.pdb_entries_couldnt_be_retrieved = The following pdb entries could not be retrieved from the PDB\:\n{0}\nPlease retry, or try downloading them manually. label.couldnt_load_file = Couldn't load file label.couldnt_find_pdb_id_in_file = Couldn't find a PDB id in the file supplied. Please enter an Id to identify this structure. @@ -490,6 +489,10 @@ label.settings_for_type = Settings for {0} label.view_full_application = View in Full Application label.load_associated_tree = Load Associated Tree... label.load_features_annotations = Load Features/Annotations... +label.load_vcf = Load SNP variants from plain text or indexed VCF data +label.load_vcf_file = Load VCF File +label.searching_vcf = Loading VCF variants... +label.added_vcf = Added {0} VCF variants to {1} sequence(s) label.export_features = Export Features... label.export_annotations = Export Annotations... label.to_upper_case = To Upper Case @@ -528,7 +531,6 @@ label.threshold_feature_above_threshold = Above Threshold label.threshold_feature_below_threshold = Below Threshold label.adjust_threshold = Adjust threshold label.toggle_absolute_relative_display_threshold = Toggle between absolute and relative display threshold. -label.display_features_same_type_different_label_using_different_colour = Display features of the same type with a different label using a different colour. (e.g. domain features) label.select_colour_minimum_value = Select Colour for Minimum Value label.select_colour_maximum_value = Select Colour for Maximum Value label.open_url_param = Open URL {0} @@ -673,7 +675,8 @@ label.2d_rna_structure_line = 2D RNA {0} (alignment) label.2d_rna_sequence_name = 2D RNA - {0} label.edit_name_and_description_current_group = Edit name and description of current group label.from_file = From File -label.enter_pdb_id = Enter PDB Id (or pdbid:chaincode) +label.enter_pdb_id = Enter PDB Id +label.enter_pdb_id_tip = Enter PDB Id (or pdbid:chaincode) label.text_colour = Text Colour... label.structure = Structure label.show_pdbstruct_dialog = 3D Structure Data... @@ -779,7 +782,7 @@ label.pairwise_aligned_sequences = Pairwise Aligned Sequences label.original_data_for_params = Original Data for {0} label.points_for_params = Points for {0} label.transformed_points_for_params = Transformed points for {0} -label.graduated_color_for_params = Graduated Feature Colour for {0} +label.variable_color_for = Variable Feature Colour for {0} label.select_background_colour = Select Background Colour label.invalid_font = Invalid Font label.separate_multiple_accession_ids = Enter one or more accession IDs separated by a semi-colon ";" @@ -866,7 +869,7 @@ label.msa_service_is_unknown = The Multiple Sequence Alignment Service named {0} label.service_called_is_not_seq_search_service = The Service called \n{0}\nis not a \nSequence Search Service\! label.seq_search_service_is_unknown = The Sequence Search Service named {0} is unknown label.feature_type = Feature Type -label.display = Display +label.show = Show label.service_url = Service URL label.copied_sequences = Copied sequences label.cut_sequences = Cut Sequences @@ -1212,7 +1215,6 @@ label.pdb_sequence_fetcher = PDB Sequence Fetcher label.result = result label.results = results label.structure_chooser = Structure Chooser -label.select = Select : label.invert = Invert label.select_pdb_file = Select PDB File info.select_filter_option = Select Filter Option/Manual Entry @@ -1319,6 +1321,45 @@ label.select_hidden_colour = Select hidden colour label.overview = Overview label.reset_to_defaults = Reset to defaults label.oview_calc = Recalculating overview... -option.enable_disable_autosearch = When ticked, search is performed automatically. +label.feature_details = Feature details +label.matchCondition_contains = Contains +label.matchCondition_notcontains = Does not contain +label.matchCondition_matches = Matches +label.matchCondition_notmatches = Does not match +label.matchCondition_present = Is present +label.matchCondition_notpresent = Is not present +label.matchCondition_eq = = +label.matchCondition_ne = not = +label.matchCondition_lt = < +label.matchCondition_le = <= +label.matchCondition_gt = > +label.matchCondition_ge = >= +label.numeric_required = The value should be numeric +label.filter = Filter +label.filters = Filters +label.join_conditions = Join conditions with +label.score = Score +label.colour_by_label = Colour by label +label.variable_colour = Variable colour... +label.select_colour = Select colour +option.enable_disable_autosearch = When ticked, search is performed automatically option.autosearch = Autosearch -label.retrieve_ids = Retrieve IDs \ No newline at end of file +label.retrieve_ids = Retrieve IDs +label.display_settings_for = Display settings for {0} features +label.simple = Simple +label.simple_colour = Simple Colour +label.colour_by_text = Colour by text +label.graduated_colour = Graduated Colour +label.by_text_of = By text of +label.by_range_of = By range of +label.filters_tooltip = Click to set or amend filters +label.or = Or +label.and = And +label.sequence_feature_colours = Sequence Feature Colours +label.best_quality = Best Quality +label.best_resolution = Best Resolution +label.most_protein_chain = Most Protein Chain +label.most_bound_molecules = Most Bound Molecules +label.most_polymer_residues = Most Polymer Residues +label.cached_structures = Cached Structures +label.free_text_search = Free Text Search diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index e8fd411..555977d 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -226,7 +226,6 @@ label.automatic_scrolling = Desplazamiento autom label.documentation = Documentación label.about = Acerca de... label.show_sequence_limits = Mostrar los límites de la secuencia -label.feature_settings = Ajustar funciones... label.all_columns = Todas las columnas label.all_sequences = Todas las secuencias label.selected_columns = Columnas seleccionadas @@ -243,6 +242,7 @@ label.apply_all_groups = Aplicar a todos los grupos label.autocalculated_annotation = Anotación autocalculada label.min_colour = Color mínimo label.max_colour = Color máximo +label.no_colour = Sin color label.use_original_colours = Usar colores originales label.threshold_minmax = El umbral es mín/máx label.represent_group_with = Representar al grupo con @@ -250,8 +250,9 @@ label.selection = Seleccionar label.group_colour = Color del grupo label.sequence = Secuencia label.view_pdb_structure = Ver estructura PDB -label.min = Mín: -label.max = Máx: +label.max_value = Valor máximo +label.min_value = Valor mínimo +label.no_value = Sin valor label.colour_by_label = Color por etiquetas label.new_feature = Nueva función label.match_case = Hacer corresponder mayúsculas y minúsculas @@ -336,6 +337,8 @@ label.optimise_order = Optimizar orden label.seq_sort_by_score = Ordenar las secuencias por puntuación label.load_colours = Cargar colores label.save_colours = Guardar colores +label.load_colours_tooltip = Cargar colores y filtros desde fichero +label.save_colours_tooltip = Guardar colores y filtros en fichero label.fetch_das_features = Recuperar funciones DAS label.selected_database_to_fetch_from = Seleccionada {0} Base de datos {1} para buscar de {2} label.database_param = Base de datos: {0} @@ -367,10 +370,6 @@ label.ignore_unmatched_dropped_files = Ignorar los ficheros sin coincidencias? label.enter_view_name = Introduzca un nombre para la vista label.enter_label = Introducir etiqueta label.enter_label_for_the_structure = Introducir una etiqueta para la estructura -label.pdb_entry_is_already_displayed = {0} Ya est\u00E1 mostrado.\nQuieres volver a usar este visor? -label.map_sequences_to_visible_window = Mapa de secuencias en ventana visible: {0} -label.add_pdbentry_to_view = Quieres a\u00F1adir {0} a la vista llamada\n{1}\n -label.align_to_existing_structure_view = Alinear a una estructura ya existente label.pdb_entries_couldnt_be_retrieved = Las siguientes entradas pdb no pueden ser extra\u00EDdas del PDB\:\n{0}\nPor favor, prueba descarg\u00E1ndolas manualmente. label.couldnt_load_file = No se pudo cargar el fichero label.couldnt_find_pdb_id_in_file = No se pudo encontrar un Id PDB en el fichero suministrado. Por favor, introduzca un Id para identificar esta estructura. @@ -456,6 +455,10 @@ label.settings_for_type = Ajustes para {0} label.view_full_application = Ver en la aplicación completa label.load_associated_tree = Cargar árbol asociado ... label.load_features_annotations = Cargar características/anotaciones ... +label.load_vcf = Cargar variantes SNP desde fichero VCF texto o tab-indexado +label.load_vcf_file = Cargar fichero VCF +label.searching_vcf = Cargando variantes VCF... +label.added_vcf= {0} variantes VCF añadidas a {1} secuencia(s) label.export_features = Exportar características... label.export_annotations = Exportar anotaciones ... label.to_upper_case = Pasar a mayúsculas @@ -489,7 +492,6 @@ label.threshold_feature_above_threshold = Por encima del umbral label.threshold_feature_below_threshold = Por debajo del umbral label.adjust_threshold = Ajustar umbral label.toggle_absolute_relative_display_threshold = Cambiar entre mostrar el umbral absoluto y el relativo. -label.display_features_same_type_different_label_using_different_colour = Mostrar las características del mismo tipo con una etiqueta diferente y empleando un color distinto (p.e. características del dominio) label.select_colour_minimum_value = Seleccionar el color para el valor mínimo label.select_colour_maximum_value = Seleccionar el color para el valor máximo label.open_url_param = Abrir URL {0} @@ -626,6 +628,7 @@ label.2d_rna_sequence_name = 2D RNA - {0} label.edit_name_and_description_current_group = Editar el nombre y la descripción del grupo actual label.from_file = desde fichero label.enter_pdb_id = Introducir PDB Id +label.enter_pdb_id_tip = Introducir PDB Id (o pdbid:chaincode) label.text_colour = Color de texto... label.structure = Estructura label.create_sequence_details_report_annotation_for = Anotación para {0} @@ -708,7 +711,7 @@ label.pairwise_aligned_sequences = Secuencias alineadas a pares label.original_data_for_params = Datos originales de {0} label.points_for_params = Puntos de {0} label.transformed_points_for_params = Puntos transformados de {0} -label.graduated_color_for_params = Color graduado para la característica de {0} +label.variable_color_for = Color variable para la característica de {0} label.select_background_colour = Seleccionar color de fondo label.invalid_font = Fuente no válida label.separate_multiple_accession_ids = Separar los accession id con un punto y coma ";" @@ -791,7 +794,7 @@ label.msa_service_is_unknown = El Servicio de Alineamiento M label.service_called_is_not_seq_search_service = El Servicio llamando \n{0}\nno es un \nServicio de B\u00FAsqueda de Secuencias\! label.seq_search_service_is_unknown = El Servicio de Búsqueda de Sencuencias llamado {0} es desconocido label.feature_type = Tipo de característisca -label.display = Representación +label.show = Mostrar label.service_url = URL del servicio label.copied_sequences = Secuencias copiadas label.cut_sequences = Cortar secuencias @@ -1171,12 +1174,12 @@ label.structures_filter=Filtro de Estructuras label.scale_protein_to_cdna=Adaptar proteína a cDNA label.scale_protein_to_cdna_tip=Hacer a los residuos de proteínas de la misma anchura que los codones en ventanas divididas status.loading_cached_pdb_entries=Cargando Entradas PDB en Caché -label.select=Seleccionar : label.select_by_annotation=Seleccionar/Ocultar Columnas por Anotación action.select_by_annotation=Seleccionar/Ocultar Columnas por Anotación... action.export_features=Exportar Características error.invalid_regex=Expresión regular inválida label.autoadd_temp=Añadir anotación factor de temperatura al alineamiento +label.double_click_to_browse = Haga doble clic para buscar fichero label.chimera_path_tip=Jalview intentará primero las rutas introducidas aquí, Y si no las rutas usuales de instalación label.structure_chooser=Selector de Estructuras label.structure_chooser_manual_association=Selector de Estructuras - asociación manual @@ -1223,13 +1226,13 @@ exception.resource_not_be_found=El recurso solicitado no se ha encontrado label.aacon_calculations=cálculos AACon label.pdb_web-service_error=Error de servicio web PDB exception.unable_to_detect_internet_connection=Jalview no puede detectar una conexión a Internet -label.chimera_path=Ruta de acceso a programa Chimera +label.chimera_path=Ruta de acceso a Chimera warn.delete_all=Borrar todas las secuencias cerrará la ventana del alineamiento.
          Confirmar o Cancelar. label.select_all=Seleccionar Todos label.alpha_helix=Hélice Alfa label.chimera_help=Ayuda para Chimera label.find_tip=Buscar alineamiento, selección o IDs de secuencia para una subsecuencia (sin huecos) -label.structure_viewer=Visualizador de estructura por defecto +label.structure_viewer=Visualizador por defecto label.embbed_biojson=Incrustar BioJSON al exportar HTML label.transparency_tip=Ajustar la transparencia a "ver a través" los colores de las características. label.choose_annotations=Escoja anotaciones @@ -1319,3 +1322,45 @@ label.select_hidden_colour = Seleccionar color de las regiones ocultas label.overview = Resumen label.reset_to_defaults = Restablecen a los predeterminados label.oview_calc = Recalculando resumen +label.feature_details = Detalles de característica +label.matchCondition_contains = Contiene +label.matchCondition_notcontains = No contiene +label.matchCondition_matches = Es igual a +label.matchCondition_notmatches = No es igual a +label.matchCondition_present = Está presente +label.matchCondition_notpresent = No está presente +label.matchCondition_eq = = +label.matchCondition_ne = not = +label.matchCondition_lt = < +label.matchCondition_le = <= +label.matchCondition_gt = > +label.matchCondition_ge = >= +label.numeric_required = Valor numérico requerido +label.filter = Filtro +label.filters = Filtros +label.join_conditions = Combinar condiciones con +label.score = Puntuación +label.colour_by_label = Colorear por texto +label.variable_colour = Color variable... +label.select_colour = Seleccionar color +option.enable_disable_autosearch = Marcar para buscar automáticamente +option.autosearch = Auto búsqueda +label.retrieve_ids = Recuperar IDs +label.display_settings_for = Visualización de características {0} +label.simple = Simple +label.simple_colour = Color simple +label.colour_by_text = Colorear por texto +label.graduated_colour = Color graduado +label.by_text_of = Por texto de +label.by_range_of = Por rango de +label.filters_tooltip = Haga clic para configurar o modificar los filtros +label.or = O +label.and = Y +label.sequence_feature_colours = Colores de características de las secuencias +label.best_quality = Mejor Calidad +label.best_resolution = Mejor Resolución +label.most_protein_chain = Más Cadena de Proteína +label.most_bound_molecules = Más Moléculas Ligadas +label.most_polymer_residues = Más Residuos de Polímeros +label.cached_structures = Estructuras en Caché +label.free_text_search = Búsqueda de texto libre diff --git a/resources/uniprot_mapping.xml b/resources/uniprot_mapping.xml index 6344d1e..68868c4 100755 --- a/resources/uniprot_mapping.xml +++ b/resources/uniprot_mapping.xml @@ -18,6 +18,7 @@ * The Jalview Authors are detailed in the 'AUTHORS' file. --> + @@ -62,13 +63,19 @@ - + - - - - + + + + + + + + + + diff --git a/schemas/JalviewUserColours.xsd b/schemas/JalviewUserColours.xsd index bd43e9d..3934d66 100755 --- a/schemas/JalviewUserColours.xsd +++ b/schemas/JalviewUserColours.xsd @@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with Jalview. If not, see . --> - - + @@ -29,13 +28,29 @@ - + + + + name of feature attribute to colour by, or attribute and sub-attribute + + + + + + Single letter residue code for an alignment colour scheme, or feature type for a feature colour scheme + + - - - loosely specified enumeration: NONE,ABOVE, or BELOW - + + + + + + + + + @@ -44,7 +59,68 @@ + + + + + + + + + + + + A feature match condition, which may be simple or compound + + + + + + + + + + + If true, matchers are AND-ed, if false they are OR-ed + + + + + + + + + + + + name of feature attribute to filter on, or attribute and sub-attribute + + + + + + + + + + + + + + + + + + + Graduated feature colour if no score (or attribute) value + + + + + + + diff --git a/schemas/jalview.xsd b/schemas/jalview.xsd index f0bd638..48824e7 100755 --- a/schemas/jalview.xsd +++ b/schemas/jalview.xsd @@ -500,6 +500,18 @@ + + + + name of feature attribute to colour by, or attribute and sub-attribute + + + + + optional filter(s) applied to the feature type + + + + @@ -559,6 +572,11 @@ + + + key2 may be used for a sub-attribute of key + + diff --git a/src/MCview/PDBChain.java b/src/MCview/PDBChain.java index f4bd31c..904a860 100755 --- a/src/MCview/PDBChain.java +++ b/src/MCview/PDBChain.java @@ -45,11 +45,11 @@ public class PDBChain public String id; - public Vector bonds = new Vector(); + public Vector bonds = new Vector<>(); - public Vector atoms = new Vector(); + public Vector atoms = new Vector<>(); - public Vector residues = new Vector(); + public Vector residues = new Vector<>(); public int offset; @@ -162,6 +162,50 @@ public class PDBChain } /** + * Annotate the residues with their corresponding positions in s1 using the + * alignment in as NOTE: This clears all atom.alignmentMapping values on the + * structure. + * + * @param as + * @param s1 + */ + public void makeExactMapping(StructureMapping mapping, SequenceI s1) + { + // first clear out any old alignmentMapping values: + for (Atom atom : atoms) + { + atom.alignmentMapping = -1; + } + SequenceI ds = s1; + while (ds.getDatasetSequence() != null) + { + ds = ds.getDatasetSequence(); + } + int pdboffset = 0; + for (Residue res : residues) + { + // res.number isn't set correctly for discontinuous/mismapped residues + int seqpos = mapping.getSeqPos(res.atoms.get(0).resNumber); + char strchar = sequence.getCharAt(pdboffset++); + if (seqpos == StructureMapping.UNASSIGNED_VALUE) + { + continue; + } + char seqchar = ds.getCharAt(seqpos - ds.getStart()); + + boolean sameResidue = Comparison.isSameResidue( + seqchar, strchar, false); + if (sameResidue) + { + for (Atom atom : res.atoms) + { + atom.alignmentMapping = seqpos - 1; + } + } + } + } + + /** * Copies over the RESNUM seqfeatures from the internal chain sequence to the * mapped sequence * @@ -299,12 +343,13 @@ public class PDBChain boolean deoxyn = false; boolean nucleotide = false; StringBuilder seq = new StringBuilder(256); - Vector resFeatures = new Vector(); - Vector resAnnotation = new Vector(); - int i, iSize = atoms.size() - 1; + Vector resFeatures = new Vector<>(); + Vector resAnnotation = new Vector<>(); + int iSize = atoms.size() - 1; int resNumber = -1; char insCode = ' '; - for (i = 0; i <= iSize; i++) + + for (int i = 0; i <= iSize; i++) { Atom tmp = atoms.elementAt(i); resNumber = tmp.resNumber; @@ -318,7 +363,7 @@ public class PDBChain offset = resNumber; } - Vector resAtoms = new Vector(); + Vector resAtoms = new Vector<>(); // Add atoms to a vector while the residue number // remains the same as the first atom's resNumber (res) while ((resNumber == res) && (ins == insCode) && (i < atoms.size())) @@ -425,7 +470,8 @@ public class PDBChain if (StructureImportSettings.isShowSeqFeatures()) { - for (i = 0, iSize = resFeatures.size(); i < iSize; i++) + iSize = resFeatures.size(); + for (int i = 0; i < iSize; i++) { sequence.addSequenceFeature(resFeatures.elementAt(i)); resFeatures.setElementAt(null, i); @@ -434,20 +480,20 @@ public class PDBChain if (visibleChainAnnotation) { Annotation[] annots = new Annotation[resAnnotation.size()]; - float max = 0; - for (i = 0, iSize = annots.length; i < iSize; i++) + float max = 0f; + float min = 0f; + iSize = annots.length; + for (int i = 0; i < iSize; i++) { annots[i] = resAnnotation.elementAt(i); - if (annots[i].value > max) - { - max = annots[i].value; - } + max = Math.max(max, annots[i].value); + min = Math.min(min, annots[i].value); resAnnotation.setElementAt(null, i); } AlignmentAnnotation tfactorann = new AlignmentAnnotation( "Temperature Factor", "Temperature Factor for " + pdbid + id, - annots, 0, max, AlignmentAnnotation.LINE_GRAPH); + annots, min, max, AlignmentAnnotation.LINE_GRAPH); tfactorann.setSequenceRef(sequence); sequence.addAlignmentAnnotation(tfactorann); } @@ -550,6 +596,12 @@ public class PDBChain { SequenceI sq = mapping.getSequence(); SequenceI dsq = sq; + if (sqmpping == null) + { + // SIFTS mappings are recorded in the StructureMapping object... + + sqmpping = mapping.getSeqToPdbMapping(); + } if (sq != null) { while (dsq.getDatasetSequence() != null) diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java index 85ae718..a910a5a 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java @@ -40,6 +40,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -83,7 +84,7 @@ public class ChimeraManager this.structureManager = structureManager; chimera = null; chimeraListenerThread = null; - currentModelsMap = new HashMap(); + currentModelsMap = new HashMap<>(); } @@ -98,7 +99,7 @@ public class ChimeraManager public List getChimeraModels(String modelName, ModelType modelType) { - List models = new ArrayList(); + List models = new ArrayList<>(); for (ChimeraModel model : currentModelsMap.values()) { if (modelName.equals(model.getModelName()) @@ -112,7 +113,7 @@ public class ChimeraManager public Map> getChimeraModelsMap() { - Map> models = new HashMap>(); + Map> models = new HashMap<>(); for (ChimeraModel model : currentModelsMap.values()) { String modelName = model.getModelName(); @@ -393,7 +394,7 @@ public class ChimeraManager public Map getSelectedModels() { - Map selectedModelsMap = new HashMap(); + Map selectedModelsMap = new HashMap<>(); List chimeraReply = sendChimeraCommand( "list selection level molecule", true); if (chimeraReply != null) @@ -418,7 +419,7 @@ public class ChimeraManager */ public List getSelectedResidueSpecs() { - List selectedResidues = new ArrayList(); + List selectedResidues = new ArrayList<>(); List chimeraReply = sendChimeraCommand( "list selection level residue", true); if (chimeraReply != null) @@ -471,7 +472,7 @@ public class ChimeraManager // TODO: [Optional] Handle smiles names in a better way in Chimera? public List getModelList() { - List modelList = new ArrayList(); + List modelList = new ArrayList<>(); List list = sendChimeraCommand("list models type molecule", true); if (list != null) @@ -494,7 +495,7 @@ public class ChimeraManager */ public List getPresets() { - ArrayList presetList = new ArrayList(); + ArrayList presetList = new ArrayList<>(); List output = sendChimeraCommand("preset list", true); if (output != null) { @@ -550,17 +551,19 @@ public class ChimeraManager // iterate over possible paths for starting Chimera for (String chimeraPath : chimeraPaths) { - File path = new File(chimeraPath); - // uncomment the next line to simulate Chimera not installed - // path = new File(chimeraPath + "x"); - if (!path.canExecute()) - { - error += "File '" + path + "' does not exist.\n"; - continue; - } try { - List args = new ArrayList(); + // ensure symbolic links are resolved + chimeraPath = Paths.get(chimeraPath).toRealPath().toString(); + File path = new File(chimeraPath); + // uncomment the next line to simulate Chimera not installed + // path = new File(chimeraPath + "x"); + if (!path.canExecute()) + { + error += "File '" + path + "' does not exist.\n"; + continue; + } + List args = new ArrayList<>(); args.add(chimeraPath); // shows Chimera output window but suppresses REST responses: // args.add("--debug"); @@ -573,7 +576,7 @@ public class ChimeraManager break; } catch (Exception e) { - // Chimera could not be started + // Chimera could not be started using this path error += e.getMessage(); } } @@ -699,7 +702,7 @@ public class ChimeraManager public List getAttrList() { - List attributes = new ArrayList(); + List attributes = new ArrayList<>(); final List reply = sendChimeraCommand("list resattr", true); if (reply != null) { @@ -718,7 +721,7 @@ public class ChimeraManager public Map getAttrValues(String aCommand, ChimeraModel model) { - Map values = new HashMap(); + Map values = new HashMap<>(); final List reply = sendChimeraCommand("list residue spec " + model.toSpec() + " attribute " + aCommand, true); if (reply != null) @@ -818,10 +821,10 @@ public class ChimeraManager protected List sendRestCommand(String command) { String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run"; - List commands = new ArrayList(1); + List commands = new ArrayList<>(1); commands.add(new BasicNameValuePair("command", command)); - List reply = new ArrayList(); + List reply = new ArrayList<>(); BufferedReader response = null; try { diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java index effe556..09a9713 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java @@ -97,7 +97,7 @@ public class StructureManager this.haveGUI = haveGUI; // Create the Chimera interface chimeraManager = new ChimeraManager(this); - chimSelectionList = new ArrayList(); + chimSelectionList = new ArrayList<>(); pathProps = new Properties(); } @@ -110,7 +110,7 @@ public class StructureManager ModelType type) { // new models - Map> newModels = new HashMap>(); + Map> newModels = new HashMap<>(); if (chimObjNames.size() > 0) { List names = chimObjNames.iterator().next(); @@ -846,7 +846,7 @@ public class StructureManager // alDialog.dispose(); // } // System.out.println("launch align dialog"); - List chimObjectList = new ArrayList(); + List chimObjectList = new ArrayList<>(); for (ChimeraModel model : chimeraManager.getChimeraModels()) { if (useChains) @@ -887,7 +887,7 @@ public class StructureManager public List getAllChimeraResidueAttributes() { - List attributes = new ArrayList(); + List attributes = new ArrayList<>(); // attributes.addAll(rinManager.getResAttrs()); attributes.addAll(chimeraManager.getAttrList()); return attributes; @@ -898,7 +898,7 @@ public class StructureManager // TODO: [Optional] Change priority of Chimera paths public static List getChimeraPaths() { - List pathList = new ArrayList(); + List pathList = new ArrayList<>(); // if no network is available and the settings have been modified by the // user, check for a @@ -934,8 +934,18 @@ public class StructureManager } else if (os.startsWith("Windows")) { - pathList.add("\\Program Files\\Chimera\\bin\\chimera"); - pathList.add("C:\\Program Files\\Chimera\\bin\\chimera.exe"); + for (String root : new String[] { "\\Program Files", + "C:\\Program Files", "\\Program Files (x86)", + "C:\\Program Files (x86)" }) + { + for (String version : new String[] { "1.11", "1.11.1", "1.11.2", + "1.12", "1.12.1", "1.12.2", "1.13" }) + { + pathList.add(root + "\\Chimera " + version + "\\bin\\chimera"); + pathList.add( + root + "\\Chimera " + version + "\\bin\\chimera.exe"); + } + } } else if (os.startsWith("Mac")) { diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index 90d9197..d1217bf 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -29,6 +29,7 @@ import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; +import jalview.datamodel.GeneLociI; import jalview.datamodel.IncompleteCodonException; import jalview.datamodel.Mapping; import jalview.datamodel.Sequence; @@ -36,6 +37,7 @@ import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.datamodel.features.SequenceFeatures; +import jalview.io.gff.Gff3Helper; import jalview.io.gff.SequenceOntologyI; import jalview.schemes.ResidueProperties; import jalview.util.Comparison; @@ -105,6 +107,15 @@ public class AlignmentUtils { return variant == null ? null : variant.getFeatureGroup(); } + + /** + * toString for aid in the debugger only + */ + @Override + public String toString() + { + return base + ":" + (variant == null ? "" : variant.getDescription()); + } } /** @@ -117,7 +128,7 @@ public class AlignmentUtils */ public static AlignmentI expandContext(AlignmentI core, int flankSize) { - List sq = new ArrayList(); + List sq = new ArrayList<>(); int maxoffset = 0; for (SequenceI s : core.getSequences()) { @@ -247,7 +258,7 @@ public class AlignmentUtils public static Map> getSequencesByName( AlignmentI al) { - Map> theMap = new LinkedHashMap>(); + Map> theMap = new LinkedHashMap<>(); for (SequenceI seq : al.getSequences()) { String name = seq.getName(); @@ -256,7 +267,7 @@ public class AlignmentUtils List seqs = theMap.get(name); if (seqs == null) { - seqs = new ArrayList(); + seqs = new ArrayList<>(); theMap.put(name, seqs); } seqs.add(seq); @@ -283,8 +294,8 @@ public class AlignmentUtils return false; } - Set mappedDna = new HashSet(); - Set mappedProtein = new HashSet(); + Set mappedDna = new HashSet<>(); + Set mappedProtein = new HashSet<>(); /* * First pass - map sequences where cross-references exist. This include @@ -384,7 +395,7 @@ public class AlignmentUtils * Answers true if the mappings include one between the given (dataset) * sequences. */ - public static boolean mappingExists(List mappings, + protected static boolean mappingExists(List mappings, SequenceI aaSeq, SequenceI cdnaSeq) { if (mappings != null) @@ -454,7 +465,7 @@ public class AlignmentUtils { String lastCodon = String.valueOf(cdnaSeqChars, cdnaLength - CODON_LENGTH, CODON_LENGTH).toUpperCase(); - for (String stop : ResidueProperties.STOP) + for (String stop : ResidueProperties.STOP_CODONS) { if (lastCodon.equals(stop)) { @@ -525,7 +536,8 @@ public class AlignmentUtils * allow * in protein to match untranslatable in dna */ final char aaRes = aaSeqChars[aaPos]; - if ((translated == null || "STOP".equals(translated)) && aaRes == '*') + if ((translated == null || ResidueProperties.STOP.equals(translated)) + && aaRes == '*') { continue; } @@ -557,7 +569,8 @@ public class AlignmentUtils if (dnaPos == cdnaSeqChars.length - CODON_LENGTH) { String codon = String.valueOf(cdnaSeqChars, dnaPos, CODON_LENGTH); - if ("STOP".equals(ResidueProperties.codonTranslate(codon))) + if (ResidueProperties.STOP + .equals(ResidueProperties.codonTranslate(codon))) { return true; } @@ -870,7 +883,7 @@ public class AlignmentUtils System.err.println("Wrong alignment type in alignProteinAsDna"); return 0; } - List unmappedProtein = new ArrayList(); + List unmappedProtein = new ArrayList<>(); Map> alignedCodons = buildCodonColumnsMap( protein, dna, unmappedProtein); return alignProteinAs(protein, alignedCodons, unmappedProtein); @@ -1081,7 +1094,7 @@ public class AlignmentUtils * {dnaSequence, {proteinSequence, codonProduct}} at that position. The * comparator keeps the codon positions ordered. */ - Map> alignedCodons = new TreeMap>( + Map> alignedCodons = new TreeMap<>( new CodonComparator()); for (SequenceI dnaSeq : dna.getSequences()) @@ -1127,9 +1140,9 @@ public class AlignmentUtils // TODO delete this ugly hack once JAL-2022 is resolved // i.e. we can model startPhase > 0 (incomplete start codon) - List sequencesChecked = new ArrayList(); + List sequencesChecked = new ArrayList<>(); AlignedCodon lastCodon = null; - Map toAdd = new HashMap(); + Map toAdd = new HashMap<>(); for (Entry> entry : alignedCodons .entrySet()) @@ -1308,7 +1321,7 @@ public class AlignmentUtils Map seqProduct = alignedCodons.get(codon); if (seqProduct == null) { - seqProduct = new HashMap(); + seqProduct = new HashMap<>(); alignedCodons.put(codon, seqProduct); } seqProduct.put(protein, codon); @@ -1445,7 +1458,7 @@ public class AlignmentUtils { continue; } - final List result = new ArrayList(); + final List result = new ArrayList<>(); for (AlignmentAnnotation dsann : datasetAnnotations) { /* @@ -1627,17 +1640,17 @@ public class AlignmentUtils throw new IllegalArgumentException( "IMPLEMENTATION ERROR: dataset.getDataset() must be null!"); } - List foundSeqs = new ArrayList(); - List cdsSeqs = new ArrayList(); + List foundSeqs = new ArrayList<>(); + List cdsSeqs = new ArrayList<>(); List mappings = dataset.getCodonFrames(); HashSet productSeqs = null; if (products != null) { - productSeqs = new HashSet(); + productSeqs = new HashSet<>(); for (SequenceI seq : products) { - productSeqs.add(seq.getDatasetSequence() == null ? seq - : seq.getDatasetSequence()); + productSeqs.add(seq.getDatasetSequence() == null ? seq : seq + .getDatasetSequence()); } } @@ -1730,9 +1743,8 @@ public class AlignmentUtils /* * add a mapping from CDS to the (unchanged) mapped to range */ - List cdsRange = Collections - .singletonList(new int[] - { 1, cdsSeq.getLength() }); + List cdsRange = Collections.singletonList(new int[] { 1, + cdsSeq.getLength() }); MapList cdsToProteinMap = new MapList(cdsRange, mapList.getToRanges(), mapList.getFromRatio(), mapList.getToRatio()); @@ -1754,7 +1766,7 @@ public class AlignmentUtils * add another mapping from original 'from' range to CDS */ AlignedCodonFrame dnaToCdsMapping = new AlignedCodonFrame(); - MapList dnaToCdsMap = new MapList(mapList.getFromRanges(), + final MapList dnaToCdsMap = new MapList(mapList.getFromRanges(), cdsRange, 1, 1); dnaToCdsMapping.addMap(dnaSeq.getDatasetSequence(), cdsSeqDss, dnaToCdsMap); @@ -1764,6 +1776,13 @@ public class AlignmentUtils } /* + * transfer dna chromosomal loci (if known) to the CDS + * sequence (via the mapping) + */ + final MapList cdsToDnaMap = dnaToCdsMap.getInverse(); + transferGeneLoci(dnaSeq, cdsToDnaMap, cdsSeq); + + /* * add DBRef with mapping from protein to CDS * (this enables Get Cross-References from protein alignment) * This is tricky because we can't have two DBRefs with the @@ -1782,26 +1801,30 @@ public class AlignmentUtils for (DBRefEntry primRef : dnaDss.getPrimaryDBRefs()) { - // creates a complementary cross-reference to the source sequence's - // primary reference. - - DBRefEntry cdsCrossRef = new DBRefEntry(primRef.getSource(), - primRef.getSource() + ":" + primRef.getVersion(), - primRef.getAccessionId()); - cdsCrossRef - .setMap(new Mapping(dnaDss, new MapList(dnaToCdsMap))); + /* + * create a cross-reference from CDS to the source sequence's + * primary reference and vice versa + */ + String source = primRef.getSource(); + String version = primRef.getVersion(); + DBRefEntry cdsCrossRef = new DBRefEntry(source, source + ":" + + version, primRef.getAccessionId()); + cdsCrossRef.setMap(new Mapping(dnaDss, new MapList(cdsToDnaMap))); cdsSeqDss.addDBRef(cdsCrossRef); + dnaSeq.addDBRef(new DBRefEntry(source, version, cdsSeq + .getName(), new Mapping(cdsSeqDss, dnaToCdsMap))); + // problem here is that the cross-reference is synthesized - // cdsSeq.getName() may be like 'CDS|dnaaccession' or // 'CDS|emblcdsacc' // assuming cds version same as dna ?!? - DBRefEntry proteinToCdsRef = new DBRefEntry(primRef.getSource(), - primRef.getVersion(), cdsSeq.getName()); + DBRefEntry proteinToCdsRef = new DBRefEntry(source, version, + cdsSeq.getName()); // - proteinToCdsRef.setMap( - new Mapping(cdsSeqDss, cdsToProteinMap.getInverse())); + proteinToCdsRef.setMap(new Mapping(cdsSeqDss, cdsToProteinMap + .getInverse())); proteinProduct.addDBRef(proteinToCdsRef); } @@ -1814,14 +1837,46 @@ public class AlignmentUtils } } - AlignmentI cds = new Alignment( - cdsSeqs.toArray(new SequenceI[cdsSeqs.size()])); + AlignmentI cds = new Alignment(cdsSeqs.toArray(new SequenceI[cdsSeqs + .size()])); cds.setDataset(dataset); return cds; } /** + * Tries to transfer gene loci (dbref to chromosome positions) from fromSeq to + * toSeq, mediated by the given mapping between the sequences + * + * @param fromSeq + * @param targetToFrom + * Map + * @param targetSeq + */ + protected static void transferGeneLoci(SequenceI fromSeq, + MapList targetToFrom, SequenceI targetSeq) + { + if (targetSeq.getGeneLoci() != null) + { + // already have - don't override + return; + } + GeneLociI fromLoci = fromSeq.getGeneLoci(); + if (fromLoci == null) + { + return; + } + + MapList newMap = targetToFrom.traverse(fromLoci.getMap()); + + if (newMap != null) + { + targetSeq.setGeneLoci(fromLoci.getSpeciesId(), + fromLoci.getAssemblyId(), fromLoci.getChromosomeId(), newMap); + } + } + + /** * A helper method that finds a CDS sequence in the alignment dataset that is * mapped to the given protein sequence, and either is, or has a mapping from, * the given dna sequence. @@ -1833,7 +1888,7 @@ public class AlignmentUtils * @param seqMappings * the set of mappings involving dnaSeq * @param aMapping - * an initial candidate from seqMappings + * a transcript-to-peptide mapping * @return */ static SequenceI findCdsForProtein(List mappings, @@ -1858,7 +1913,15 @@ public class AlignmentUtils if (mappedFromLength == dnaLength || mappedFromLength == dnaLength - CODON_LENGTH) { - return seqDss; + /* + * if sequence has CDS features, this is a transcript with no UTR + * - do not take this as the CDS sequence! (JAL-2789) + */ + if (seqDss.getFeatures().getFeaturesByOntology(SequenceOntologyI.CDS) + .isEmpty()) + { + return seqDss; + } } /* @@ -1883,10 +1946,12 @@ public class AlignmentUtils { /* * found a 3:1 mapping to the protein product which covers - * the whole dna sequence i.e. is from CDS; finally check it - * is from the dna start sequence + * the whole dna sequence i.e. is from CDS; finally check the CDS + * is mapped from the given dna start sequence */ SequenceI cdsSeq = map.getFromSeq(); + // todo this test is weak if seqMappings contains multiple mappings; + // we get away with it if transcript:cds relationship is 1:1 List dnaToCdsMaps = MappingUtils .findMappingsForSequence(cdsSeq, seqMappings); if (!dnaToCdsMaps.isEmpty()) @@ -1989,21 +2054,23 @@ public class AlignmentUtils } /** - * add any DBRefEntrys to cdsSeq from contig that have a Mapping congruent to + * Adds any DBRefEntrys to cdsSeq from contig that have a Mapping congruent to * the given mapping. * * @param cdsSeq * @param contig + * @param proteinProduct * @param mapping - * @return list of DBRefEntrys added. + * @return list of DBRefEntrys added */ - public static List propagateDBRefsToCDS(SequenceI cdsSeq, + protected static List propagateDBRefsToCDS(SequenceI cdsSeq, SequenceI contig, SequenceI proteinProduct, Mapping mapping) { - // gather direct refs from contig congrent with mapping - List direct = new ArrayList(); - HashSet directSources = new HashSet(); + // gather direct refs from contig congruent with mapping + List direct = new ArrayList<>(); + HashSet directSources = new HashSet<>(); + if (contig.getDBRefs() != null) { for (DBRefEntry dbr : contig.getDBRefs()) @@ -2023,7 +2090,7 @@ public class AlignmentUtils DBRefEntry[] onSource = DBRefUtils.selectRefs( proteinProduct.getDBRefs(), directSources.toArray(new String[0])); - List propagated = new ArrayList(); + List propagated = new ArrayList<>(); // and generate appropriate mappings for (DBRefEntry cdsref : direct) @@ -2081,7 +2148,7 @@ public class AlignmentUtils * subtypes in the Sequence Ontology) * @param omitting */ - public static int transferFeatures(SequenceI fromSeq, SequenceI toSeq, + protected static int transferFeatures(SequenceI fromSeq, SequenceI toSeq, MapList mapping, String select, String... omitting) { SequenceI copyTo = toSeq; @@ -2180,12 +2247,13 @@ public class AlignmentUtils int mappedDnaLength = MappingUtils.getLength(ranges); /* - * if not a whole number of codons, something is wrong, - * abort mapping + * if not a whole number of codons, truncate mapping */ - if (mappedDnaLength % CODON_LENGTH > 0) + int codonRemainder = mappedDnaLength % CODON_LENGTH; + if (codonRemainder > 0) { - return null; + mappedDnaLength -= codonRemainder; + MappingUtils.removeEndPositions(codonRemainder, ranges); } int proteinLength = proteinSeq.getLength(); @@ -2202,7 +2270,7 @@ public class AlignmentUtils proteinStart++; proteinLength--; } - List proteinRange = new ArrayList(); + List proteinRange = new ArrayList<>(); /* * dna length should map to protein (or protein plus stop codon) @@ -2235,9 +2303,9 @@ public class AlignmentUtils * @param dnaSeq * @return */ - public static List findCdsPositions(SequenceI dnaSeq) + protected static List findCdsPositions(SequenceI dnaSeq) { - List result = new ArrayList(); + List result = new ArrayList<>(); List sfs = dnaSeq.getFeatures().getFeaturesByOntology( SequenceOntologyI.CDS); @@ -2370,15 +2438,22 @@ public class AlignmentUtils { if (var.variant != null) { - String alleles = (String) var.variant.getValue("alleles"); + String alleles = (String) var.variant.getValue(Gff3Helper.ALLELES); if (alleles != null) { for (String base : alleles.split(",")) { - String codon = base + base2 + base3; - if (addPeptideVariant(peptide, peptidePos, residue, var, codon)) + if (!base1.equalsIgnoreCase(base)) { - count++; + String codon = base.toUpperCase() + base2.toLowerCase() + + base3.toLowerCase(); + String canonical = base1.toUpperCase() + base2.toLowerCase() + + base3.toLowerCase(); + if (addPeptideVariant(peptide, peptidePos, residue, var, + codon, canonical)) + { + count++; + } } } } @@ -2392,15 +2467,22 @@ public class AlignmentUtils { if (var.variant != null) { - String alleles = (String) var.variant.getValue("alleles"); + String alleles = (String) var.variant.getValue(Gff3Helper.ALLELES); if (alleles != null) { for (String base : alleles.split(",")) { - String codon = base1 + base + base3; - if (addPeptideVariant(peptide, peptidePos, residue, var, codon)) + if (!base2.equalsIgnoreCase(base)) { - count++; + String codon = base1.toLowerCase() + base.toUpperCase() + + base3.toLowerCase(); + String canonical = base1.toLowerCase() + base2.toUpperCase() + + base3.toLowerCase(); + if (addPeptideVariant(peptide, peptidePos, residue, var, + codon, canonical)) + { + count++; + } } } } @@ -2414,15 +2496,22 @@ public class AlignmentUtils { if (var.variant != null) { - String alleles = (String) var.variant.getValue("alleles"); + String alleles = (String) var.variant.getValue(Gff3Helper.ALLELES); if (alleles != null) { for (String base : alleles.split(",")) { - String codon = base1 + base2 + base; - if (addPeptideVariant(peptide, peptidePos, residue, var, codon)) + if (!base3.equalsIgnoreCase(base)) { - count++; + String codon = base1.toLowerCase() + base2.toLowerCase() + + base.toUpperCase(); + String canonical = base1.toLowerCase() + base2.toLowerCase() + + base3.toUpperCase(); + if (addPeptideVariant(peptide, peptidePos, residue, var, + codon, canonical)) + { + count++; + } } } } @@ -2433,20 +2522,22 @@ public class AlignmentUtils } /** - * Helper method that adds a peptide variant feature, provided the given codon - * translates to a value different to the current residue (is a non-synonymous - * variant). ID and clinical_significance attributes of the dna variant (if - * present) are copied to the new feature. + * Helper method that adds a peptide variant feature. ID and + * clinical_significance attributes of the dna variant (if present) are copied + * to the new feature. * * @param peptide * @param peptidePos * @param residue * @param var * @param codon + * the variant codon e.g. aCg + * @param canonical + * the 'normal' codon e.g. aTg * @return true if a feature was added, else false */ static boolean addPeptideVariant(SequenceI peptide, int peptidePos, - String residue, DnaVariant var, String codon) + String residue, DnaVariant var, String codon, String canonical) { /* * get peptide translation of codon e.g. GAT -> D @@ -2454,62 +2545,79 @@ public class AlignmentUtils * e.g. multibase variants or HGMD_MUTATION etc * are currently ignored here */ - String trans = codon.contains("-") ? "-" + String trans = codon.contains("-") ? null : (codon.length() > CODON_LENGTH ? null : ResidueProperties.codonTranslate(codon)); - if (trans != null && !trans.equals(residue)) + if (trans == null) + { + return false; + } + String desc = canonical + "/" + codon; + String featureType = ""; + if (trans.equals(residue)) + { + featureType = SequenceOntologyI.SYNONYMOUS_VARIANT; + } + else if (ResidueProperties.STOP.equals(trans)) + { + featureType = SequenceOntologyI.STOP_GAINED; + } + else { String residue3Char = StringUtils .toSentenceCase(ResidueProperties.aa2Triplet.get(residue)); String trans3Char = StringUtils .toSentenceCase(ResidueProperties.aa2Triplet.get(trans)); - String desc = "p." + residue3Char + peptidePos + trans3Char; - SequenceFeature sf = new SequenceFeature( - SequenceOntologyI.SEQUENCE_VARIANT, desc, peptidePos, - peptidePos, var.getSource()); - StringBuilder attributes = new StringBuilder(32); - String id = (String) var.variant.getValue(ID); - if (id != null) - { - if (id.startsWith(SEQUENCE_VARIANT)) - { - id = id.substring(SEQUENCE_VARIANT.length()); - } - sf.setValue(ID, id); - attributes.append(ID).append("=").append(id); - // TODO handle other species variants JAL-2064 - StringBuilder link = new StringBuilder(32); - try - { - link.append(desc).append(" ").append(id).append( - "|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=") - .append(URLEncoder.encode(id, "UTF-8")); - sf.addLink(link.toString()); - } catch (UnsupportedEncodingException e) - { - // as if - } - } - String clinSig = (String) var.variant.getValue(CLINICAL_SIGNIFICANCE); - if (clinSig != null) + desc = "p." + residue3Char + peptidePos + trans3Char; + featureType = SequenceOntologyI.NONSYNONYMOUS_VARIANT; + } + SequenceFeature sf = new SequenceFeature(featureType, desc, peptidePos, + peptidePos, var.getSource()); + + StringBuilder attributes = new StringBuilder(32); + String id = (String) var.variant.getValue(ID); + if (id != null) + { + if (id.startsWith(SEQUENCE_VARIANT)) { - sf.setValue(CLINICAL_SIGNIFICANCE, clinSig); - attributes.append(";").append(CLINICAL_SIGNIFICANCE).append("=") - .append(clinSig); + id = id.substring(SEQUENCE_VARIANT.length()); } - peptide.addSequenceFeature(sf); - if (attributes.length() > 0) + sf.setValue(ID, id); + attributes.append(ID).append("=").append(id); + // TODO handle other species variants JAL-2064 + StringBuilder link = new StringBuilder(32); + try + { + link.append(desc).append(" ").append(id).append( + "|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=") + .append(URLEncoder.encode(id, "UTF-8")); + sf.addLink(link.toString()); + } catch (UnsupportedEncodingException e) { - sf.setAttributes(attributes.toString()); + // as if } - return true; } - return false; + String clinSig = (String) var.variant.getValue(CLINICAL_SIGNIFICANCE); + if (clinSig != null) + { + sf.setValue(CLINICAL_SIGNIFICANCE, clinSig); + attributes.append(";").append(CLINICAL_SIGNIFICANCE).append("=") + .append(clinSig); + } + peptide.addSequenceFeature(sf); + if (attributes.length() > 0) + { + sf.setAttributes(attributes.toString()); + } + return true; } /** * Builds a map whose key is position in the protein sequence, and value is a - * list of the base and all variants for each corresponding codon position + * list of the base and all variants for each corresponding codon position. + *

          + * This depends on dna variants being held as a comma-separated list as + * property "alleles" on variant features. * * @param dnaSeq * @param dnaToProtein @@ -2523,7 +2631,7 @@ public class AlignmentUtils * map from peptide position to all variants of the codon which codes for it * LinkedHashMap ensures we keep the peptide features in sequence order */ - LinkedHashMap[]> variants = new LinkedHashMap[]>(); + LinkedHashMap[]> variants = new LinkedHashMap<>(); List dnaFeatures = dnaSeq.getFeatures() .getFeaturesByOntology(SequenceOntologyI.SEQUENCE_VARIANT); @@ -2547,6 +2655,30 @@ public class AlignmentUtils // not handling multi-locus variant features continue; } + + /* + * ignore variant if not a SNP + */ + String alls = (String) sf.getValue(Gff3Helper.ALLELES); + if (alls == null) + { + continue; // non-SNP VCF variant perhaps - can't process this + } + + String[] alleles = alls.toUpperCase().split(","); + boolean isSnp = true; + for (String allele : alleles) + { + if (allele.trim().length() > 1) + { + isSnp = false; + } + } + if (!isSnp) + { + continue; + } + int[] mapsTo = dnaToProtein.locateInTo(dnaCol, dnaCol); if (mapsTo == null) { @@ -2558,28 +2690,13 @@ public class AlignmentUtils if (codonVariants == null) { codonVariants = new ArrayList[CODON_LENGTH]; - codonVariants[0] = new ArrayList(); - codonVariants[1] = new ArrayList(); - codonVariants[2] = new ArrayList(); + codonVariants[0] = new ArrayList<>(); + codonVariants[1] = new ArrayList<>(); + codonVariants[2] = new ArrayList<>(); variants.put(peptidePosition, codonVariants); } /* - * extract dna variants to a string array - */ - String alls = (String) sf.getValue("alleles"); - if (alls == null) - { - continue; - } - String[] alleles = alls.toUpperCase().split(","); - int i = 0; - for (String allele : alleles) - { - alleles[i++] = allele.trim(); // lose any space characters "A, G" - } - - /* * get this peptide's codon positions e.g. [3, 4, 5] or [4, 7, 10] */ int[] codon = peptidePosition == lastPeptidePostion ? lastCodon @@ -2699,7 +2816,7 @@ public class AlignmentUtils /* * fancy case - aligning via mappings between sequences */ - List unmapped = new ArrayList(); + List unmapped = new ArrayList<>(); Map> columnMap = buildMappedColumnsMap( unaligned, aligned, unmapped); int width = columnMap.size(); @@ -2774,7 +2891,7 @@ public class AlignmentUtils } // map from dataset sequence to alignment sequence(s) - Map> alignedDatasets = new HashMap>(); + Map> alignedDatasets = new HashMap<>(); for (SequenceI seq : aligned.getSequences()) { SequenceI ds = seq.getDatasetSequence(); @@ -2837,7 +2954,7 @@ public class AlignmentUtils * {unalignedSequence, characterPerSequence} at that position. * TreeMap keeps the entries in ascending column order. */ - SortedMap> map = new TreeMap>(); + SortedMap> map = new TreeMap<>(); /* * record any sequences that have no mapping so can't be realigned @@ -2942,7 +3059,7 @@ public class AlignmentUtils Map seqsMap = map.get(fromCol); if (seqsMap == null) { - seqsMap = new HashMap(); + seqsMap = new HashMap<>(); map.put(fromCol, seqsMap); } seqsMap.put(seq, seq.getCharAt(mappedCharPos - toStart)); diff --git a/src/jalview/analysis/Conservation.java b/src/jalview/analysis/Conservation.java index 131b39c..0af5d20 100755 --- a/src/jalview/analysis/Conservation.java +++ b/src/jalview/analysis/Conservation.java @@ -273,7 +273,7 @@ public class Conservation * or not conserved (-1) * Using TreeMap means properties are displayed in alphabetical order */ - SortedMap resultHash = new TreeMap(); + SortedMap resultHash = new TreeMap<>(); SymbolCounts symbolCounts = values.getSymbolCounts(); char[] symbols = symbolCounts.symbols; int[] counts = symbolCounts.values; @@ -567,7 +567,7 @@ public class Conservation */ private void percentIdentity(ScoreMatrix sm) { - seqNums = new Vector(); + seqNums = new Vector<>(); int i = 0, iSize = sequences.length; // Do we need to calculate this again? for (i = 0; i < iSize; i++) @@ -622,7 +622,7 @@ public class Conservation protected void findQuality(int startCol, int endCol, ScoreMatrix scoreMatrix) { - quality = new Vector(); + quality = new Vector<>(); double max = -Double.MAX_VALUE; float[][] scores = scoreMatrix.getMatrix(); @@ -721,8 +721,8 @@ public class Conservation /** * Complete the given consensus and quuality annotation rows. Note: currently - * this method will enlarge the given annotation row if it is too small, - * otherwise will leave its length unchanged. + * this method will reallocate the given annotation row if it is different to + * the calculated width, otherwise will leave its length unchanged. * * @param conservation * conservation annotation row @@ -754,7 +754,7 @@ public class Conservation float qmax = 0f; if (conservation != null && conservation.annotations != null - && conservation.annotations.length < alWidth) + && conservation.annotations.length != alWidth) { conservation.annotations = new Annotation[alWidth]; } @@ -763,7 +763,7 @@ public class Conservation { quality2.graphMax = (float) qualityMaximum; if (quality2.annotations != null - && quality2.annotations.length < alWidth) + && quality2.annotations.length != alWidth) { quality2.annotations = new Annotation[alWidth]; } diff --git a/src/jalview/analysis/Dna.java b/src/jalview/analysis/Dna.java index a10b037..2ad8487 100644 --- a/src/jalview/analysis/Dna.java +++ b/src/jalview/analysis/Dna.java @@ -44,6 +44,7 @@ import jalview.util.ShiftList; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.Iterator; import java.util.List; public class Dna @@ -56,19 +57,23 @@ public class Dna * 'final' variables describe the inputs to the translation, which should not * be modified. */ - final private List selection; + private final List selection; - final private String[] seqstring; + private final String[] seqstring; - final private int[] contigs; + private final Iterator contigs; - final private char gapChar; + private final char gapChar; - final private AlignmentAnnotation[] annotations; + private final AlignmentAnnotation[] annotations; - final private int dnaWidth; + private final int dnaWidth; - final private AlignmentI dataset; + private final AlignmentI dataset; + + private ShiftList vismapping; + + private int[] startcontigs; /* * Working variables for the translation. @@ -91,7 +96,7 @@ public class Dna * @param viewport * @param visibleContigs */ - public Dna(AlignViewportI viewport, int[] visibleContigs) + public Dna(AlignViewportI viewport, Iterator visibleContigs) { this.selection = Arrays.asList(viewport.getSequenceSelection()); this.seqstring = viewport.getViewAsString(true); @@ -100,6 +105,45 @@ public class Dna this.annotations = viewport.getAlignment().getAlignmentAnnotation(); this.dnaWidth = viewport.getAlignment().getWidth(); this.dataset = viewport.getAlignment().getDataset(); + initContigs(); + } + + /** + * Initialise contigs used as starting point for translateCodingRegion + */ + private void initContigs() + { + vismapping = new ShiftList(); // map from viscontigs to seqstring + // intervals + + int npos = 0; + int[] lastregion = null; + ArrayList tempcontigs = new ArrayList<>(); + while (contigs.hasNext()) + { + int[] region = contigs.next(); + if (lastregion == null) + { + vismapping.addShift(npos, region[0]); + } + else + { + // hidden region + vismapping.addShift(npos, region[0] - lastregion[1] + 1); + } + lastregion = region; + tempcontigs.add(region[0]); + tempcontigs.add(region[1]); + } + + startcontigs = new int[tempcontigs.size()]; + int i = 0; + for (Integer val : tempcontigs) + { + startcontigs[i] = val; + i++; + } + tempcontigs = null; } /** @@ -161,7 +205,7 @@ public class Dna int s; int sSize = selection.size(); - List pepseqs = new ArrayList(); + List pepseqs = new ArrayList<>(); for (s = 0; s < sSize; s++) { SequenceI newseq = translateCodingRegion(selection.get(s), @@ -213,7 +257,7 @@ public class Dna if (dnarefs != null) { // intersect with pep - List mappedrefs = new ArrayList(); + List mappedrefs = new ArrayList<>(); DBRefEntry[] refs = dna.getDBRefs(); for (int d = 0; d < refs.length; d++) { @@ -391,27 +435,14 @@ public class Dna String seqstring, AlignedCodonFrame acf, List proteinSeqs) { - List skip = new ArrayList(); - int skipint[] = null; - ShiftList vismapping = new ShiftList(); // map from viscontigs to seqstring - // intervals - int vc; - int[] scontigs = new int[contigs.length]; + List skip = new ArrayList<>(); + int[] skipint = null; + int npos = 0; - for (vc = 0; vc < contigs.length; vc += 2) - { - if (vc == 0) - { - vismapping.addShift(npos, contigs[vc]); - } - else - { - // hidden region - vismapping.addShift(npos, contigs[vc] - contigs[vc - 1] + 1); - } - scontigs[vc] = contigs[vc]; - scontigs[vc + 1] = contigs[vc + 1]; - } + int vc = 0; + + int[] scontigs = new int[startcontigs.length]; + System.arraycopy(startcontigs, 0, scontigs, 0, startcontigs.length); // allocate a roughly sized buffer for the protein sequence StringBuilder protein = new StringBuilder(seqstring.length() / 2); @@ -544,7 +575,7 @@ public class Dna skip.add(skipint); skipint = null; } - if (aa.equals("STOP")) + if (aa.equals(ResidueProperties.STOP)) { aa = STOP_ASTERIX; } @@ -800,7 +831,7 @@ public class Dna public AlignmentI reverseCdna(boolean complement) { int sSize = selection.size(); - List reversed = new ArrayList(); + List reversed = new ArrayList<>(); for (int s = 0; s < sSize; s++) { SequenceI newseq = reverseSequence(selection.get(s).getName(), @@ -851,6 +882,23 @@ public class Dna } /** + * Answers the reverse complement of the input string + * + * @see #getComplement(char) + * @param s + * @return + */ + public static String reverseComplement(String s) + { + StringBuilder sb = new StringBuilder(s.length()); + for (int i = s.length() - 1; i >= 0; i--) + { + sb.append(Dna.getComplement(s.charAt(i))); + } + return sb.toString(); + } + + /** * Returns dna complement (preserving case) for aAcCgGtTuU. Ambiguity codes * are treated as on http://reverse-complement.com/. Anything else is left * unchanged. diff --git a/src/jalview/analysis/Rna.java b/src/jalview/analysis/Rna.java index 0d39abf..e5cda93 100644 --- a/src/jalview/analysis/Rna.java +++ b/src/jalview/analysis/Rna.java @@ -440,8 +440,8 @@ public class Rna /* * catch things like <<..<<..>>..<<..>>>> | */ - int j = bps.size() - 1; - while (j >= 0) + int j = bps.size(); + while (--j >= 0) { int popen = bps.get(j).getBP5(); @@ -460,7 +460,6 @@ public class Rna break; } } - j -= 1; } // Put positions and helix information into the hashtable diff --git a/src/jalview/api/FeatureColourI.java b/src/jalview/api/FeatureColourI.java index 0ded079..4dbb1bb 100644 --- a/src/jalview/api/FeatureColourI.java +++ b/src/jalview/api/FeatureColourI.java @@ -56,6 +56,14 @@ public interface FeatureColourI Color getMaxColour(); /** + * Returns the 'no value' colour (used when a feature lacks score, or the + * attribute, being used for colouring) + * + * @return + */ + Color getNoColour(); + + /** * Answers true if the feature has a single colour, i.e. if isColourByLabel() * and isGraduatedColour() both answer false * @@ -64,7 +72,8 @@ public interface FeatureColourI boolean isSimpleColour(); /** - * Answers true if the feature is coloured by label (description) + * Answers true if the feature is coloured by label (description) or by text + * value of an attribute * * @return */ @@ -93,18 +102,6 @@ public interface FeatureColourI void setAboveThreshold(boolean b); /** - * Answers true if the threshold is the minimum value (when - * isAboveThreshold()) or maximum value (when isBelowThreshold()) of the - * colour range; only applicable when isGraduatedColour and either - * isAboveThreshold() or isBelowThreshold() answers true - * - * @return - */ - boolean isThresholdMinMax(); - - void setThresholdMinMax(boolean b); - - /** * Returns the threshold value (if any), else zero * * @return @@ -156,7 +153,10 @@ public interface FeatureColourI Color getColor(SequenceFeature feature); /** - * Update the min-max range for a graduated colour scheme + * Update the min-max range for a graduated colour scheme. Note that the + * colour scheme may be configured to colour by feature score, or a + * (numeric-valued) attribute - the caller should ensure that the correct + * range is being set. * * @param min * @param max @@ -169,4 +169,27 @@ public interface FeatureColourI * @return */ String toJalviewFormat(String featureType); + + /** + * Answers true if colour is by attribute text or numerical value + * + * @return + */ + boolean isColourByAttribute(); + + /** + * Answers the name of the attribute (and optional sub-attribute...) used for + * colouring if any, or null + * + * @return + */ + String[] getAttributeName(); + + /** + * Sets the name of the attribute (and optional sub-attribute...) used for + * colouring if any, or null to remove this property + * + * @return + */ + void setAttributeName(String... name); } diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index 9d2d7f4..cf3c8da 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -22,6 +22,7 @@ package jalview.api; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSetI; import java.awt.Color; import java.awt.Graphics; @@ -132,7 +133,7 @@ public interface FeatureRenderer List getGroups(boolean visible); /** - * change visibility for a range of groups + * Set visibility for a list of groups * * @param toset * @param visible @@ -140,7 +141,7 @@ public interface FeatureRenderer void setGroupVisibility(List toset, boolean visible); /** - * change visibiilty of given group + * Set visibility of the given feature group * * @param group * @param visible @@ -148,9 +149,9 @@ public interface FeatureRenderer void setGroupVisibility(String group, boolean visible); /** - * 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). + * Returns visible 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 sequence * @param column @@ -215,4 +216,53 @@ public interface FeatureRenderer */ float getTransparency(); + /** + * Answers the filters applied to the given feature type, or null if none is + * set + * + * @param featureType + * @return + */ + FeatureMatcherSetI getFeatureFilter(String featureType); + + /** + * Answers the feature filters map + * + * @return + */ + public Map getFeatureFilters(); + + /** + * Sets the filters for the feature type, or removes them if a null or empty + * filter is passed + * + * @param featureType + * @param filter + */ + void setFeatureFilter(String featureType, FeatureMatcherSetI filter); + + /** + * Replaces all feature filters with the given map + * + * @param filters + */ + void setFeatureFilters(Map filters); + + /** + * Returns the colour for a particular feature instance. This includes + * calculation of 'colour by label', or of a graduated score colour, if + * applicable. + *

          + * Returns null if + *

            + *
          • feature type is not visible, or
          • + *
          • feature group is not visible, or
          • + *
          • feature values lie outside any colour threshold, or
          • + *
          • feature is excluded by filter conditions
          • + *
          + * + * @param feature + * @return + */ + Color getColour(SequenceFeature feature); } diff --git a/src/jalview/api/structures/JalviewStructureDisplayI.java b/src/jalview/api/structures/JalviewStructureDisplayI.java index fd66388..8f778f7 100644 --- a/src/jalview/api/structures/JalviewStructureDisplayI.java +++ b/src/jalview/api/structures/JalviewStructureDisplayI.java @@ -20,6 +20,9 @@ */ package jalview.api.structures; +import jalview.api.AlignmentViewPanel; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SequenceI; import jalview.schemes.ColourSchemeI; import jalview.structures.models.AAStructureBindingModel; @@ -62,4 +65,64 @@ public interface JalviewStructureDisplayI */ void setJalviewColourScheme(ColourSchemeI colourScheme); + /** + * + * @return true if all background sequence/structure binding threads have + * completed for this viewer instance + */ + boolean hasMapping(); + + /** + * Checks if the PDB file is already loaded in this viewer, if so just adds + * mappings as necessary and answers true, else answers false. This supports + * the use case of adding additional chains of the same structure to a viewer. + * + * @param seq + * @param chains + * @param apanel + * @param pdbId + * @return + */ + boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains, + AlignmentViewPanel apanel, String pdbId); + + /** + * Adds one or more chains (sequences) of a PDB structure to this structure + * viewer + * + * @param pdbentry + * @param seq + * @param chains + * @param apanel + * @param pdbId + * @return + */ + void addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq, + String[] chains, AlignmentViewPanel apanel, String pdbId); + + /** + * refresh GUI after reconfiguring structure(s) and alignment panels + */ + void updateTitleAndMenus(); + + /** + * Answers true if the viewer should attempt to align any added structures, + * else false + * + * @return + */ + boolean isAlignAddedStructures(); + + /** + * Sets the flag for whether added structures should be aligned + * + * @param alignAdded + */ + void setAlignAddedStructures(boolean alignAdded); + + /** + * Raise the panel to the top of the stack... + */ + void raiseViewer(); + } diff --git a/src/jalview/appletgui/APopupMenu.java b/src/jalview/appletgui/APopupMenu.java index 46bd4fd..76f2705 100644 --- a/src/jalview/appletgui/APopupMenu.java +++ b/src/jalview/appletgui/APopupMenu.java @@ -901,10 +901,7 @@ public class APopupMenu extends java.awt.PopupMenu .formatMessage("label.annotation_for_displayid", new Object[] { seq.getDisplayId(true) })); new SequenceAnnotationReport(null).createSequenceAnnotationReport( - contents, seq, true, true, - (ap.seqPanel.seqCanvas.fr != null) - ? ap.seqPanel.seqCanvas.fr.getMinMax() - : null); + contents, seq, true, true, ap.seqPanel.seqCanvas.fr); contents.append("

          "); } Frame frame = new Frame(); diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index ef87671..5ad212e 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -1445,9 +1445,10 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, FeaturesFile formatter = new FeaturesFile(); if (format.equalsIgnoreCase("Jalview")) { - features = formatter.printJalviewFormat(viewport.getAlignment() - .getSequencesArray(), getDisplayedFeatureCols(), - getDisplayedFeatureGroups(), true); + features = formatter.printJalviewFormat( + viewport.getAlignment().getSequencesArray(), + getDisplayedFeatureCols(), null, getDisplayedFeatureGroups(), + true); } else { @@ -1905,7 +1906,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, static StringBuffer copiedSequences; - static Vector copiedHiddenColumns; + static HiddenColumns copiedHiddenColumns; protected void copy_actionPerformed() { @@ -1929,14 +1930,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, if (viewport.hasHiddenColumns() && viewport.getSelectionGroup() != null) { - copiedHiddenColumns = new Vector<>(viewport.getAlignment() - .getHiddenColumns().getHiddenColumnsCopy()); int hiddenOffset = viewport.getSelectionGroup().getStartRes(); - for (int[] region : copiedHiddenColumns) - { - region[0] = region[0] - hiddenOffset; - region[1] = region[1] - hiddenOffset; - } + int hiddenCutoff = viewport.getSelectionGroup().getEndRes(); + + // create new HiddenColumns object with copy of hidden regions + // between startRes and endRes, offset by startRes + copiedHiddenColumns = new HiddenColumns( + viewport.getAlignment().getHiddenColumns(), hiddenOffset, + hiddenCutoff, hiddenOffset); } else { @@ -2005,13 +2006,13 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, { try { - if (copiedSequences == null) { return; } - StringTokenizer st = new StringTokenizer(copiedSequences.toString()); + StringTokenizer st = new StringTokenizer(copiedSequences.toString(), + "\t"); Vector seqs = new Vector(); while (st.hasMoreElements()) { @@ -2043,14 +2044,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, } AlignFrame af = new AlignFrame(new Alignment(newSeqs), viewport.applet, newtitle, false); - if (copiedHiddenColumns != null) - { - for (int i = 0; i < copiedHiddenColumns.size(); i++) - { - int[] region = copiedHiddenColumns.elementAt(i); - af.viewport.hideColumns(region[0], region[1]); - } - } + af.viewport.setHiddenColumns(copiedHiddenColumns); jalview.bin.JalviewLite.addFrame(af, newtitle, frameWidth, frameHeight); diff --git a/src/jalview/appletgui/AlignmentPanel.java b/src/jalview/appletgui/AlignmentPanel.java index 270b2f7..83d8ade 100644 --- a/src/jalview/appletgui/AlignmentPanel.java +++ b/src/jalview/appletgui/AlignmentPanel.java @@ -421,8 +421,8 @@ public class AlignmentPanel extends Panel if (av.hasHiddenColumns()) { AlignmentI al = av.getAlignment(); - start = al.getHiddenColumns().findColumnPosition(ostart); - end = al.getHiddenColumns().findColumnPosition(end); + start = al.getHiddenColumns().absoluteToVisibleColumn(ostart); + end = al.getHiddenColumns().absoluteToVisibleColumn(end); if (start == end) { if (!scrollToNearest && !al.getHiddenColumns().isVisible(ostart)) @@ -675,7 +675,7 @@ public class AlignmentPanel extends Panel if (av.hasHiddenColumns()) { width = av.getAlignment().getHiddenColumns() - .findColumnPosition(width); + .absoluteToVisibleColumn(width); } if (x < 0) { diff --git a/src/jalview/appletgui/AnnotationLabels.java b/src/jalview/appletgui/AnnotationLabels.java index d8f65a5..1366f31 100755 --- a/src/jalview/appletgui/AnnotationLabels.java +++ b/src/jalview/appletgui/AnnotationLabels.java @@ -23,6 +23,7 @@ package jalview.appletgui; import jalview.analysis.AlignmentUtils; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.Annotation; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.util.MessageManager; @@ -31,6 +32,7 @@ import jalview.util.ParseHtmlBodyAndLinks; import java.awt.Checkbox; import java.awt.CheckboxMenuItem; import java.awt.Color; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.FontMetrics; @@ -50,13 +52,22 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.Arrays; import java.util.Collections; -import java.util.Vector; public class AnnotationLabels extends Panel implements ActionListener, MouseListener, MouseMotionListener { Image image; + /** + * width in pixels within which height adjuster arrows are shown and active + */ + private static final int HEIGHT_ADJUSTER_WIDTH = 50; + + /** + * height in pixels for allowing height adjuster to be active + */ + private static int HEIGHT_ADJUSTER_HEIGHT = 10; + boolean active = false; AlignmentPanel ap; @@ -92,23 +103,6 @@ public class AnnotationLabels extends Panel this.ap = ap; this.av = ap.av; setLayout(null); - - /** - * this retrieves the adjustable height glyph from resources. we don't use - * it at the moment. java.net.URL url = - * getClass().getResource("/images/idwidth.gif"); Image temp = null; - * - * if (url != null) { temp = - * java.awt.Toolkit.getDefaultToolkit().createImage(url); } - * - * try { MediaTracker mt = new MediaTracker(this); mt.addImage(temp, 0); - * mt.waitForID(0); } catch (Exception ex) { } - * - * BufferedImage bi = new BufferedImage(temp.getHeight(this), - * temp.getWidth(this), BufferedImage.TYPE_INT_RGB); Graphics2D g = - * (Graphics2D) bi.getGraphics(); g.rotate(Math.toRadians(90)); - * g.drawImage(temp, 0, -bi.getWidth(this), this); image = (Image) bi; - */ addMouseListener(this); addMouseMotionListener(this); } @@ -208,7 +202,9 @@ public class AnnotationLabels extends Panel } else if (evt.getActionCommand().equals(COPYCONS_SEQ)) { - SequenceI cons = av.getConsensusSeq(); + SequenceGroup group = aa[selectedRow].groupRef; + SequenceI cons = group == null ? av.getConsensusSeq() + : group.getConsensusSeq(); if (cons != null) { copy_annotseqtoclipboard(cons); @@ -268,7 +264,10 @@ public class AnnotationLabels extends Panel @Override public void mouseMoved(MouseEvent evt) { - resizePanel = evt.getY() < 10 && evt.getX() < 14; + resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT + && evt.getX() < HEIGHT_ADJUSTER_WIDTH; + setCursor(Cursor.getPredefinedCursor( + resizePanel ? Cursor.S_RESIZE_CURSOR : Cursor.DEFAULT_CURSOR)); int row = getSelectedRow(evt.getY() + scrollOffset); if (row > -1) @@ -406,6 +405,7 @@ public class AnnotationLabels extends Panel resizePanel = false; dragEvent = null; dragCancelled = false; + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); repaint(); ap.annotationPanel.repaint(); } @@ -418,6 +418,8 @@ public class AnnotationLabels extends Panel resizePanel = true; repaint(); } + setCursor(Cursor.getPredefinedCursor( + resizePanel ? Cursor.S_RESIZE_CURSOR : Cursor.DEFAULT_CURSOR)); } @Override @@ -843,8 +845,8 @@ public class AnnotationLabels extends Panel + "\t" + sq.getSequenceAsString() + "\n"); if (av.hasHiddenColumns()) { - jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector<>( - av.getAlignment().getHiddenColumns().getHiddenColumnsCopy()); + jalview.appletgui.AlignFrame.copiedHiddenColumns = new HiddenColumns( + av.getAlignment().getHiddenColumns()); } } @@ -903,14 +905,8 @@ public class AnnotationLabels extends Panel } } g.translate(0, +scrollOffset); - if (resizePanel) - { - g.setColor(Color.red); - g.setPaintMode(); - g.drawLine(2, 8, 5, 2); - g.drawLine(5, 2, 8, 8); - } - else if (!dragCancelled && dragEvent != null && aa != null) + + if (!resizePanel && !dragCancelled && dragEvent != null && aa != null) { g.setColor(Color.lightGray); g.drawString(aa[selectedRow].label, dragEvent.getX(), diff --git a/src/jalview/appletgui/AnnotationPanel.java b/src/jalview/appletgui/AnnotationPanel.java index 50a9e33..50bc184 100755 --- a/src/jalview/appletgui/AnnotationPanel.java +++ b/src/jalview/appletgui/AnnotationPanel.java @@ -480,7 +480,7 @@ public class AnnotationPanel extends Panel if (av.hasHiddenColumns()) { column = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(column); + .visibleToAbsoluteColumn(column); } if (row > -1 && column < aa[row].annotations.length diff --git a/src/jalview/appletgui/AppletJmolBinding.java b/src/jalview/appletgui/AppletJmolBinding.java index 2f61b24..e5767b6 100644 --- a/src/jalview/appletgui/AppletJmolBinding.java +++ b/src/jalview/appletgui/AppletJmolBinding.java @@ -24,7 +24,6 @@ import jalview.api.AlignmentViewPanel; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.ext.jmol.JalviewJmolBinding; -import jalview.gui.IProgressIndicator; import jalview.io.DataSourceType; import jalview.structure.StructureSelectionManager; @@ -174,21 +173,12 @@ class AppletJmolBinding extends JalviewJmolBinding @Override public int[] resizeInnerPanel(String data) { - // TODO Auto-generated method stub return null; } @Override public Map getJSpecViewProperty(String arg0) { - // TODO Auto-generated method stub - return null; - } - - @Override - protected IProgressIndicator getIProgressIndicator() - { - // no progress indicators on the applet return null; } } diff --git a/src/jalview/appletgui/ExtJmol.java b/src/jalview/appletgui/ExtJmol.java index 89228d5..b0d3f7a 100644 --- a/src/jalview/appletgui/ExtJmol.java +++ b/src/jalview/appletgui/ExtJmol.java @@ -26,7 +26,6 @@ import jalview.api.SequenceRenderer; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.ext.jmol.JalviewJmolBinding; -import jalview.gui.IProgressIndicator; import jalview.io.DataSourceType; import java.awt.Container; @@ -66,18 +65,8 @@ public class ExtJmol extends JalviewJmolBinding } @Override - protected IProgressIndicator getIProgressIndicator() - { - // no progress indicators on applet (could access javascript for this) - return null; - } - - @Override public void updateColours(Object source) { - - // TODO Auto-generated method stub - } @Override @@ -190,7 +179,6 @@ public class ExtJmol extends JalviewJmolBinding protected JmolAppConsoleInterface createJmolConsole( Container consolePanel, String buttonsToShow) { - // TODO Auto-generated method stub return null; } @@ -205,14 +193,11 @@ public class ExtJmol extends JalviewJmolBinding @Override public void releaseReferences(Object svl) { - // TODO Auto-generated method stub - } @Override public Map getJSpecViewProperty(String arg0) { - // TODO Auto-generated method stub return null; } diff --git a/src/jalview/appletgui/FeatureColourChooser.java b/src/jalview/appletgui/FeatureColourChooser.java index 5a073c6..d9eae11 100644 --- a/src/jalview/appletgui/FeatureColourChooser.java +++ b/src/jalview/appletgui/FeatureColourChooser.java @@ -58,6 +58,8 @@ public class FeatureColourChooser extends Panel implements ActionListener, */ private static final int SCALE_FACTOR_1K = 1000; + private static final String COLON = ":"; + private JVDialog frame; private Frame owner; @@ -167,9 +169,9 @@ public class FeatureColourChooser extends Panel implements ActionListener, slider.addAdjustmentListener(this); slider.addMouseListener(this); owner = (af != null) ? af : fs.frame; - frame = new JVDialog(owner, MessageManager - .formatMessage("label.graduated_color_for_params", new String[] - { type }), true, 480, 248); + frame = new JVDialog(owner, MessageManager.formatMessage( + "label.variable_color_for", new String[] { type }), true, 480, + 248); frame.setMainPanel(this); validate(); frame.setVisible(true); @@ -198,8 +200,10 @@ public class FeatureColourChooser extends Panel implements ActionListener, private void jbInit() throws Exception { - Label minLabel = new Label(MessageManager.getString("label.min")), - maxLabel = new Label(MessageManager.getString("label.max")); + Label minLabel = new Label( + MessageManager.getString("label.min_value") + COLON); + Label maxLabel = new Label( + MessageManager.getString("label.max_value") + COLON); minLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); maxLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); // minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); diff --git a/src/jalview/appletgui/FeatureSettings.java b/src/jalview/appletgui/FeatureSettings.java index 9a67499..a60aacd 100755 --- a/src/jalview/appletgui/FeatureSettings.java +++ b/src/jalview/appletgui/FeatureSettings.java @@ -25,6 +25,7 @@ import jalview.api.FeatureSettingsControllerI; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import jalview.util.MessageManager; +import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import java.awt.BorderLayout; import java.awt.Button; @@ -65,7 +66,7 @@ import java.util.Set; public class FeatureSettings extends Panel implements ItemListener, MouseListener, MouseMotionListener, - ActionListener, AdjustmentListener, FeatureSettingsControllerI + AdjustmentListener, FeatureSettingsControllerI { FeatureRenderer fr; @@ -120,8 +121,17 @@ public class FeatureSettings extends Panel add(scrollPane, BorderLayout.CENTER); } - Button invert = new Button("Invert Selection"); - invert.addActionListener(this); + Button invert = new Button( + MessageManager.getString("label.invert_selection")); + invert.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + invertSelection(); + } + }); Panel lowerPanel = new Panel(new GridLayout(2, 1, 5, 10)); lowerPanel.add(invert); @@ -545,8 +555,7 @@ public class FeatureSettings extends Panel } } - @Override - public void actionPerformed(ActionEvent evt) + protected void invertSelection() { for (int i = 0; i < featurePanel.getComponentCount(); i++) { @@ -583,22 +592,19 @@ public class FeatureSettings extends Panel { Component[] comps = featurePanel.getComponents(); int cSize = comps.length; - - Object[][] tmp = new Object[cSize][3]; - int tmpSize = 0; - for (int i = 0; i < cSize; i++) - { - MyCheckbox check = (MyCheckbox) comps[i]; - tmp[tmpSize][0] = check.type; - tmp[tmpSize][1] = fr.getFeatureStyle(check.type); - tmp[tmpSize][2] = new Boolean(check.getState()); - tmpSize++; + FeatureSettingsBean[] rowData = new FeatureSettingsBean[cSize]; + int i = 0; + for (Component comp : comps) + { + MyCheckbox check = (MyCheckbox) comp; + // feature filter set to null as not (yet) offered in applet + FeatureColourI colour = fr.getFeatureStyle(check.type); + rowData[i] = new FeatureSettingsBean(check.type, colour, null, + check.getState()); + i++; } - Object[][] data = new Object[tmpSize][3]; - System.arraycopy(tmp, 0, data, 0, tmpSize); - - fr.setFeaturePriority(data); + fr.setFeaturePriority(rowData); ap.paintAlignment(updateOverview, updateOverview); } diff --git a/src/jalview/appletgui/IdCanvas.java b/src/jalview/appletgui/IdCanvas.java index f5ea12e..296f898 100755 --- a/src/jalview/appletgui/IdCanvas.java +++ b/src/jalview/appletgui/IdCanvas.java @@ -286,7 +286,7 @@ public class IdCanvas extends Panel implements ViewportListenerI if (av.hasHiddenColumns()) { maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth) - 1; + .absoluteToVisibleColumn(maxwidth) - 1; } int annotationHeight = 0; diff --git a/src/jalview/appletgui/IdwidthAdjuster.java b/src/jalview/appletgui/IdwidthAdjuster.java index 75e3243..2602268 100755 --- a/src/jalview/appletgui/IdwidthAdjuster.java +++ b/src/jalview/appletgui/IdwidthAdjuster.java @@ -21,9 +21,8 @@ package jalview.appletgui; import java.awt.Color; +import java.awt.Cursor; import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Image; import java.awt.Panel; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; @@ -36,29 +35,24 @@ public class IdwidthAdjuster extends Panel int oldX = 0; - Image image; - AlignmentPanel ap; public IdwidthAdjuster(AlignmentPanel ap) { setLayout(null); this.ap = ap; - java.net.URL url = getClass().getResource("/images/idwidth.gif"); - if (url != null) - { - image = java.awt.Toolkit.getDefaultToolkit().getImage(url); - } - + setBackground(Color.WHITE); addMouseListener(this); addMouseMotionListener(this); } + @Override public void mousePressed(MouseEvent evt) { oldX = evt.getX(); } + @Override public void mouseReleased(MouseEvent evt) { active = false; @@ -85,18 +79,24 @@ public class IdwidthAdjuster extends Panel // } } + @Override public void mouseEntered(MouseEvent evt) { active = true; + setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)); + repaint(); } + @Override public void mouseExited(MouseEvent evt) { active = false; + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); repaint(); } + @Override public void mouseDragged(MouseEvent evt) { active = true; @@ -112,25 +112,13 @@ public class IdwidthAdjuster extends Panel } } + @Override public void mouseMoved(MouseEvent evt) { } + @Override public void mouseClicked(MouseEvent evt) { } - - public void paint(Graphics g) - { - g.setColor(Color.white); - g.fillRect(0, 0, getSize().width, getSize().height); - if (active) - { - if (image != null) - { - g.drawImage(image, getSize().width - 20, 2, this); - } - } - } - } diff --git a/src/jalview/appletgui/OverviewCanvas.java b/src/jalview/appletgui/OverviewCanvas.java index 9597b44..e99c021 100644 --- a/src/jalview/appletgui/OverviewCanvas.java +++ b/src/jalview/appletgui/OverviewCanvas.java @@ -128,11 +128,9 @@ public class OverviewCanvas extends Component { mg.translate(0, od.getSequencesHeight()); or.drawGraph(mg, av.getAlignmentConservationAnnotation(), - av.getCharWidth(), od.getGraphHeight(), - od.getColumns(av.getAlignment())); + od.getGraphHeight(), od.getColumns(av.getAlignment())); mg.translate(0, -od.getSequencesHeight()); } - System.gc(); if (restart) { diff --git a/src/jalview/appletgui/OverviewPanel.java b/src/jalview/appletgui/OverviewPanel.java index 8ce597d..3bbbe95 100755 --- a/src/jalview/appletgui/OverviewPanel.java +++ b/src/jalview/appletgui/OverviewPanel.java @@ -155,6 +155,10 @@ public class OverviewPanel extends Panel implements Runnable, if (!od.isPositionInBox(evt.getX(), evt.getY())) { draggingBox = false; + + // display drag cursor at mouse position + setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment().getHiddenSequences(), av.getAlignment().getHiddenColumns()); @@ -172,6 +176,7 @@ public class OverviewPanel extends Panel implements Runnable, @Override public void mouseReleased(MouseEvent evt) { + draggingBox = false; } @Override diff --git a/src/jalview/appletgui/ScalePanel.java b/src/jalview/appletgui/ScalePanel.java index 04fb22b..c91449f 100755 --- a/src/jalview/appletgui/ScalePanel.java +++ b/src/jalview/appletgui/ScalePanel.java @@ -42,6 +42,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.beans.PropertyChangeEvent; +import java.util.Iterator; import java.util.List; public class ScalePanel extends Panel @@ -86,7 +87,7 @@ public class ScalePanel extends Panel if (av.hasHiddenColumns()) { - res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x); + res = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(x); } else { @@ -173,7 +174,7 @@ public class ScalePanel extends Panel }); pop.add(item); - if (av.getAlignment().getHiddenColumns().hasManyHiddenColumns()) + if (av.getAlignment().getHiddenColumns().hasMultiHiddenColumnRegions()) { item = new MenuItem(MessageManager.getString("action.reveal_all")); item.addActionListener(new ActionListener() @@ -234,7 +235,7 @@ public class ScalePanel extends Panel if (av.hasHiddenColumns()) { res = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(res); + .visibleToAbsoluteColumn(res); } if (!stretchingGroup) @@ -275,7 +276,7 @@ public class ScalePanel extends Panel int res = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes(); res = Math.max(0, res); - res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res); + res = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(res); res = Math.min(res, av.getAlignment().getWidth() - 1); min = Math.min(res, min); max = Math.max(res, max); @@ -377,7 +378,7 @@ public class ScalePanel extends Panel { if (hidden.isVisible(sel)) { - sel = hidden.findColumnPosition(sel); + sel = hidden.absoluteToVisibleColumn(sel); } else { @@ -436,24 +437,17 @@ public class ScalePanel extends Panel if (av.getShowHiddenMarkers()) { int widthx = 1 + endx - startx; - List positions = hidden.findHiddenRegionPositions(); - for (int pos : positions) + Iterator it = hidden.getStartRegionIterator(startx, + startx + widthx + 1); + while (it.hasNext()) { - - res = pos - startx; - - if (res < 0 || res > widthx) - { - continue; - } + res = it.next() - startx; gg.fillPolygon( new int[] - { -1 + res * avCharWidth - avcharHeight / 4, - -1 + res * avCharWidth + avcharHeight / 4, - -1 + res * avCharWidth }, - new int[] - { y, y, y + 2 * yOf }, 3); + { -1 + res * avCharWidth - avcharHeight / 4, -1 + res * avCharWidth + avcharHeight / 4, + -1 + res * avCharWidth }, new int[] + { y, y, y + 2 * yOf }, 3); } } } diff --git a/src/jalview/appletgui/SeqCanvas.java b/src/jalview/appletgui/SeqCanvas.java index 2420cf7..35d73de 100755 --- a/src/jalview/appletgui/SeqCanvas.java +++ b/src/jalview/appletgui/SeqCanvas.java @@ -25,6 +25,7 @@ import jalview.datamodel.HiddenColumns; import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; +import jalview.datamodel.VisibleContigsIterator; import jalview.renderer.ScaleRenderer; import jalview.renderer.ScaleRenderer.ScaleMark; import jalview.viewmodel.AlignmentViewport; @@ -37,7 +38,7 @@ import java.awt.Graphics; import java.awt.Image; import java.awt.Panel; import java.beans.PropertyChangeEvent; -import java.util.List; +import java.util.Iterator; public class SeqCanvas extends Panel implements ViewportListenerI { @@ -130,16 +131,16 @@ public class SeqCanvas extends Panel implements ViewportListenerI if (av.hasHiddenColumns()) { startx = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(startx); + .visibleToAbsoluteColumn(startx); endx = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(endx); + .visibleToAbsoluteColumn(endx); } int maxwidth = av.getAlignment().getWidth(); if (av.hasHiddenColumns()) { maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth) - 1; + .absoluteToVisibleColumn(maxwidth) - 1; } // WEST SCALE @@ -180,7 +181,7 @@ public class SeqCanvas extends Panel implements ViewportListenerI if (av.hasHiddenColumns()) { endx = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(endx); + .visibleToAbsoluteColumn(endx); } SequenceI seq; @@ -417,71 +418,71 @@ public class SeqCanvas extends Panel implements ViewportListenerI int canvasHeight, int startRes) { AlignmentI al = av.getAlignment(); - + FontMetrics fm = getFontMetrics(av.getFont()); - + LABEL_EAST = 0; LABEL_WEST = 0; - + if (av.getScaleRightWrapped()) { LABEL_EAST = fm.stringWidth(getMask()); } - + if (av.getScaleLeftWrapped()) { LABEL_WEST = fm.stringWidth(getMask()); } - + int hgap = avcharHeight; if (av.getScaleAboveWrapped()) { hgap += avcharHeight; } - + int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth; int cHeight = av.getAlignment().getHeight() * avcharHeight; - + av.setWrappedWidth(cWidth); - + av.getRanges().setViewportStartAndWidth(startRes, cWidth); - + int endx; int ypos = hgap; - + int maxwidth = av.getAlignment().getWidth(); - + if (av.hasHiddenColumns()) { maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth); + .absoluteToVisibleColumn(maxwidth); } - + while ((ypos <= canvasHeight) && (startRes < maxwidth)) { endx = startRes + cWidth - 1; - + if (endx > maxwidth) { endx = maxwidth; } - + g.setColor(Color.black); - + if (av.getScaleLeftWrapped()) { drawWestScale(g, startRes, endx, ypos); } - + if (av.getScaleRightWrapped()) { g.translate(canvasWidth - LABEL_EAST, 0); drawEastScale(g, startRes, endx, ypos); g.translate(-(canvasWidth - LABEL_EAST), 0); } - + g.translate(LABEL_WEST, 0); - + if (av.getScaleAboveWrapped()) { drawNorthScale(g, startRes, endx, ypos); @@ -491,37 +492,27 @@ public class SeqCanvas extends Panel implements ViewportListenerI HiddenColumns hidden = av.getAlignment().getHiddenColumns(); g.setColor(Color.blue); int res; - List positions = hidden.findHiddenRegionPositions(); - for (int pos : positions) + Iterator it = hidden.getStartRegionIterator(startRes, + endx + 1); + while (it.hasNext()) { - res = pos - startRes; - - if (res < 0 || res > endx - startRes) - { - continue; - } - + res = it.next() - startRes; gg.fillPolygon( new int[] - { res * avcharWidth - avcharHeight / 4, - res * avcharWidth + avcharHeight / 4, - res * avcharWidth }, + { res * avcharWidth - avcharHeight / 4, res * avcharWidth + avcharHeight / 4, res * avcharWidth }, new int[] - { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), - ypos - (avcharHeight / 2) + 8 }, - 3); - + { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), ypos - (avcharHeight / 2) + 8 }, 3); } } - + if (g.getClip() == null) { g.setClip(0, 0, cWidth * avcharWidth, canvasHeight); } - + drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos); g.setClip(null); - + if (av.isShowAnnotation()) { g.translate(0, cHeight + ypos + 4); @@ -529,17 +520,17 @@ public class SeqCanvas extends Panel implements ViewportListenerI { annotations = new AnnotationPanel(av); } - + annotations.drawComponent(g, startRes, endx + 1); g.translate(0, -cHeight - ypos - 4); } g.translate(-LABEL_WEST, 0); - + ypos += cHeight + getAnnotationHeight() + hgap; - + startRes += cWidth; } - + } AnnotationPanel annotations; @@ -570,70 +561,44 @@ public class SeqCanvas extends Panel implements ViewportListenerI else { int screenY = 0; - final int screenYMax = endRes - startRes; - int blockStart = startRes; - int blockEnd = endRes; - - if (av.hasHiddenColumns()) - { - HiddenColumns hidden = av.getAlignment().getHiddenColumns(); - for (int[] region : hidden.getHiddenColumnsCopy()) - { - int hideStart = region[0]; - int hideEnd = region[1]; - - if (hideStart <= blockStart) - { - blockStart += (hideEnd - hideStart) + 1; - continue; - } - - /* - * draw up to just before the next hidden region, or the end of - * the visible region, whichever comes first - */ - blockEnd = Math.min(hideStart - 1, blockStart + screenYMax - - screenY); - - g1.translate(screenY * avcharWidth, 0); + int blockStart; + int blockEnd; - draw(g1, blockStart, blockEnd, startSeq, endSeq, offset); + HiddenColumns hidden = av.getAlignment().getHiddenColumns(); + VisibleContigsIterator regions = (VisibleContigsIterator) hidden + .getVisContigsIterator(startRes, endRes + 1, true); - /* - * draw the downline of the hidden column marker (ScalePanel draws the - * triangle on top) if we reached it - */ - if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1) - { - g1.setColor(Color.blue); - g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1, - 0 + offset, - (blockEnd - blockStart + 1) * avcharWidth - 1, - (endSeq - startSeq + 1) * avcharHeight + offset); - } - - g1.translate(-screenY * avcharWidth, 0); - screenY += blockEnd - blockStart + 1; - blockStart = hideEnd + 1; - - if (screenY > screenYMax) - { - // already rendered last block - return; - } - } - } - if (screenY <= screenYMax) + while (regions.hasNext()) { - // remaining visible region to render - blockEnd = blockStart + (endRes - startRes) - screenY; + int[] region = regions.next(); + blockEnd = region[1]; + blockStart = region[0]; + + /* + * draw up to just before the next hidden region, or the end of + * the visible region, whichever comes first + */ g1.translate(screenY * avcharWidth, 0); + draw(g1, blockStart, blockEnd, startSeq, endSeq, offset); + /* + * draw the downline of the hidden column marker (ScalePanel draws the + * triangle on top) if we reached it + */ + if (av.getShowHiddenMarkers() + && (regions.hasNext() || regions.endsAtHidden())) + { + g1.setColor(Color.blue); + g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1, + 0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1, + (endSeq - startSeq + 1) * avcharHeight + offset); + } + g1.translate(-screenY * avcharWidth, 0); + screenY += blockEnd - blockStart + 1; } } - } // int startRes, int endRes, int startSeq, int endSeq, int x, int y, diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index d74bbb7..e07dae6 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -647,7 +647,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, if (av.hasHiddenColumns()) { res = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(res); + .visibleToAbsoluteColumn(res); } return res; @@ -1123,9 +1123,9 @@ public class SeqPanel extends Panel implements MouseMotionListener, { fixedColumns = true; int y1 = av.getAlignment().getHiddenColumns() - .getHiddenBoundaryLeft(startres); + .getNextHiddenBoundary(true, startres); int y2 = av.getAlignment().getHiddenColumns() - .getHiddenBoundaryRight(startres); + .getNextHiddenBoundary(false, startres); if ((insertGap && startres > y1 && lastres < y1) || (!insertGap && startres < y2 && lastres > y2)) @@ -1197,7 +1197,8 @@ public class SeqPanel extends Panel implements MouseMotionListener, if (sg.getSize() == av.getAlignment().getHeight()) { if ((av.hasHiddenColumns() && startres < av.getAlignment() - .getHiddenColumns().getHiddenBoundaryRight(startres))) + .getHiddenColumns() + .getNextHiddenBoundary(false, startres))) { endEditing(); return; diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index dc50843..83bc810 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -28,8 +28,6 @@ import jalview.schemes.UserColourScheme; import jalview.structure.StructureImportSettings; import jalview.urls.IdOrgSettings; import jalview.util.ColorUtils; -import jalview.ws.dbsources.das.api.DasSourceRegistryI; -import jalview.ws.dbsources.das.datamodel.DasSourceRegistry; import jalview.ws.sifts.SiftsSettings; import java.awt.Color; @@ -281,7 +279,7 @@ public class Cache @Override public synchronized Enumeration keys() { - return Collections.enumeration(new TreeSet(super.keySet())); + return Collections.enumeration(new TreeSet<>(super.keySet())); } }; @@ -334,7 +332,10 @@ public class Cache } } - /** Called when Jalview is started */ + /** + * Loads properties from the given properties file. Any existing properties + * are first cleared. + */ public static void loadProperties(String propsFile) { propertiesFile = propsFile; @@ -369,6 +370,7 @@ public class Cache { fis = new FileInputStream(propertiesFile); } + applicationProperties.clear(); applicationProperties.load(fis); // remove any old build properties @@ -621,14 +623,14 @@ public class Cache * @param obj * String value of property * - * @return String value of property + * @return previous value of property (or null) */ - public static String setProperty(String key, String obj) + public static Object setProperty(String key, String obj) { - + Object oldValue = null; try { - applicationProperties.setProperty(key, obj); + oldValue = applicationProperties.setProperty(key, obj); if (!propsAreReadOnly) { FileOutputStream out = new FileOutputStream(propertiesFile); @@ -640,7 +642,7 @@ public class Cache System.out.println( "Error setting property: " + key + " " + obj + "\n" + ex); } - return obj; + return oldValue; } /** @@ -978,22 +980,6 @@ public class Cache return null; } - private static DasSourceRegistryI sourceRegistry = null; - - /** - * initialise and .. - * - * @return instance of the das source registry - */ - public static DasSourceRegistryI getDasSourceRegistry() - { - if (sourceRegistry == null) - { - sourceRegistry = new DasSourceRegistry(); - } - return sourceRegistry; - } - /** * Set the specified value, or remove it if null or empty. Does not save the * properties file. diff --git a/src/jalview/bin/Jalview.java b/src/jalview/bin/Jalview.java index 9ec0033..3270144 100755 --- a/src/jalview/bin/Jalview.java +++ b/src/jalview/bin/Jalview.java @@ -20,9 +20,6 @@ */ package jalview.bin; -import groovy.lang.Binding; -import groovy.util.GroovyScriptEngine; - import jalview.ext.so.SequenceOntology; import jalview.gui.AlignFrame; import jalview.gui.Desktop; @@ -64,12 +61,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Vector; +import javax.swing.LookAndFeel; import javax.swing.UIManager; +import groovy.lang.Binding; +import groovy.util.GroovyScriptEngine; + /** * Main class for Jalview Application
          *
          - * start with java -Djava.ext.dirs=$PATH_TO_LIB$ jalview.bin.Jalview + * start with: java -classpath "$PATH_TO_LIB$/*:$PATH_TO_CLASSES$" \ + * jalview.bin.Jalview + * + * or on Windows: java -classpath "$PATH_TO_LIB$/*;$PATH_TO_CLASSES$" \ + * jalview.bin.Jalview jalview.bin.Jalview + * + * (ensure -classpath arg is quoted to avoid shell expansion of '*' and do not + * embellish '*' to e.g. '*.jar') * * @author $author$ * @version $Revision$ @@ -148,7 +156,6 @@ public class Jalview af.setProgressBar(MessageManager .getString("status.das_features_being_retrived"), id); af.featureSettings_actionPerformed(null); - af.featureSettings.fetchDasFeatures(dasSources, true); af.setProgressBar(null, id); synchronized (us) { @@ -264,7 +271,7 @@ public class Jalview { error.printStackTrace(); System.out.println("\nEssential logging libraries not found." - + "\nUse: java -Djava.ext.dirs=$PATH_TO_LIB$ jalview.bin.Jalview"); + + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview"); System.exit(0); } @@ -275,20 +282,43 @@ public class Jalview UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ex) { + System.err.println("Unexpected Look and Feel Exception"); + ex.printStackTrace(); } if (Platform.isAMac()) { + + LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager + .getLookAndFeel(); System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Jalview"); System.setProperty("apple.laf.useScreenMenuBar", "true"); - try + if (lookAndFeel != null) { - UIManager.setLookAndFeel( - ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel()); - } catch (Throwable e) + try + { + UIManager.setLookAndFeel(lookAndFeel); + } catch (Throwable e) + { + System.err.println( + "Failed to set QuaQua look and feel: " + e.toString()); + } + } + if (lookAndFeel == null || !(lookAndFeel.getClass() + .isAssignableFrom(UIManager.getLookAndFeel().getClass())) + || !UIManager.getLookAndFeel().getClass().toString() + .toLowerCase().contains("quaqua")) { - System.err.println( - "Failed to set QuaQua look and feel: " + e.toString()); + try + { + System.err.println( + "Quaqua LaF not available on this plaform. Using VAqua(4).\nSee https://issues.jalview.org/browse/JAL-2976"); + UIManager.setLookAndFeel("org.violetlib.aqua.AquaLookAndFeel"); + } catch (Throwable e) + { + System.err.println( + "Failed to reset look and feel: " + e.toString()); + } } } @@ -970,7 +1000,7 @@ public class Jalview } try { - Map vbinding = new HashMap(); + Map vbinding = new HashMap<>(); vbinding.put("Jalview", this); if (af != null) { @@ -1036,7 +1066,7 @@ public class Jalview + nickname + "|" + url); if (source == null) { - source = new Vector(); + source = new Vector<>(); } source.addElement(nickname); } @@ -1054,7 +1084,7 @@ public class Jalview System.out.println("adding source '" + data + "'"); if (source == null) { - source = new Vector(); + source = new Vector<>(); } source.addElement(data); } diff --git a/src/jalview/bin/JalviewLite.java b/src/jalview/bin/JalviewLite.java index 6504290..a60496c 100644 --- a/src/jalview/bin/JalviewLite.java +++ b/src/jalview/bin/JalviewLite.java @@ -31,7 +31,6 @@ import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentOrder; import jalview.datamodel.ColumnSelection; -import jalview.datamodel.HiddenColumns; import jalview.datamodel.PDBEntry; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; @@ -471,7 +470,7 @@ public class JalviewLite extends Applet SequenceI rs = sel.getSequenceAt(0); start = rs.findIndex(start); end = rs.findIndex(end); - List cs = new ArrayList(csel.getSelected()); + List cs = new ArrayList<>(csel.getSelected()); csel.clear(); for (Integer selectedCol : cs) { @@ -921,7 +920,7 @@ public class JalviewLite extends Applet setMouseoverListener(currentAlignFrame, listener); } - private Vector javascriptListeners = new Vector(); + private Vector javascriptListeners = new Vector<>(); /* * (non-Javadoc) @@ -2165,8 +2164,8 @@ public class JalviewLite extends Applet else { param = st.nextToken(); - List tmp = new ArrayList(); - List tmp2 = new ArrayList(); + List tmp = new ArrayList<>(); + List tmp2 = new ArrayList<>(); while (st.hasMoreTokens()) { @@ -2279,12 +2278,9 @@ public class JalviewLite extends Applet JnetAnnotationMaker.add_annotation(predictions, alignFrame.viewport.getAlignment(), 0, false); // false == do not add sequence profile from concise output - SequenceI repseq = alignFrame.viewport.getAlignment() - .getSequenceAt(0); - alignFrame.viewport.getAlignment().setSeqrep(repseq); - HiddenColumns cs = new HiddenColumns(); - cs.hideInsertionsFor(repseq); - alignFrame.viewport.getAlignment().setHiddenColumns(cs); + + alignFrame.viewport.getAlignment().setupJPredAlignment(); + alignFrame.alignPanel.fontChanged(); alignFrame.alignPanel.setScrollValues(0, 0); result = true; @@ -2802,9 +2798,9 @@ public class JalviewLite extends Applet // callInitCallback(); } - private Hashtable jshashes = new Hashtable(); + private Hashtable jshashes = new Hashtable<>(); - private Hashtable> jsmessages = new Hashtable>(); + private Hashtable> jsmessages = new Hashtable<>(); public void setJsMessageSet(String messageclass, String viewId, String[] colcommands) @@ -2812,7 +2808,7 @@ public class JalviewLite extends Applet Hashtable msgset = jsmessages.get(messageclass); if (msgset == null) { - msgset = new Hashtable(); + msgset = new Hashtable<>(); jsmessages.put(messageclass, msgset); } msgset.put(viewId, colcommands); diff --git a/src/jalview/binding/Colour.java b/src/jalview/binding/Colour.java index 25cf9bf..f51e9af 100644 --- a/src/jalview/binding/Colour.java +++ b/src/jalview/binding/Colour.java @@ -27,7 +27,8 @@ public class Colour implements java.io.Serializable // --------------------------/ /** - * Field _name. + * Single letter residue code for an alignment colour scheme, or feature type + * for a feature colour scheme */ private java.lang.String _name; @@ -42,9 +43,15 @@ public class Colour implements java.io.Serializable private java.lang.String _minRGB; /** - * loosely specified enumeration: NONE,ABOVE, or BELOW + * Field _noValueColour. */ - private java.lang.String _threshType; + private jalview.binding.types.NoValueColour _noValueColour = jalview.binding.types.NoValueColour + .valueOf("Min"); + + /** + * Field _threshType. + */ + private jalview.binding.types.ColourThreshTypeType _threshType; /** * Field _threshold. @@ -96,6 +103,11 @@ public class Colour implements java.io.Serializable */ private boolean _has_autoScale; + /** + * name of feature attribute to colour by, or attribute and sub-attribute + */ + private java.util.Vector _attributeNameList; + // ----------------/ // - Constructors -/ // ----------------/ @@ -103,6 +115,8 @@ public class Colour implements java.io.Serializable public Colour() { super(); + setNoValueColour(jalview.binding.types.NoValueColour.valueOf("Min")); + this._attributeNameList = new java.util.Vector(); } // -----------/ @@ -110,41 +124,140 @@ public class Colour implements java.io.Serializable // -----------/ /** - */ + * + * + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.addElement(vAttributeName); + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.add(index, vAttributeName); + } + + /** + */ public void deleteAutoScale() { this._has_autoScale = false; } /** - */ + */ public void deleteColourByLabel() { this._has_colourByLabel = false; } /** - */ + */ public void deleteMax() { this._has_max = false; } /** - */ + */ public void deleteMin() { this._has_min = false; } /** - */ + */ public void deleteThreshold() { this._has_threshold = false; } /** + * Method enumerateAttributeName. + * + * @return an Enumeration over all java.lang.String elements + */ + public java.util.Enumeration enumerateAttributeName() + { + return this._attributeNameList.elements(); + } + + /** + * Method getAttributeName. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the java.lang.String at the given index + */ + public java.lang.String getAttributeName(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("getAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + return (java.lang.String) _attributeNameList.get(index); + } + + /** + * Method getAttributeName.Returns the contents of the collection in an Array. + *

          + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public java.lang.String[] getAttributeName() + { + java.lang.String[] array = new java.lang.String[0]; + return (java.lang.String[]) this._attributeNameList.toArray(array); + } + + /** + * Method getAttributeNameCount. + * + * @return the size of this collection + */ + public int getAttributeNameCount() + { + return this._attributeNameList.size(); + } + + /** * Returns the value of field 'autoScale'. * * @return the value of field 'AutoScale'. @@ -195,7 +308,9 @@ public class Colour implements java.io.Serializable } /** - * Returns the value of field 'name'. + * Returns the value of field 'name'. The field 'name' has the following + * description: Single letter residue code for an alignment colour scheme, or + * feature type for a feature colour scheme * * @return the value of field 'Name'. */ @@ -205,6 +320,16 @@ public class Colour implements java.io.Serializable } /** + * Returns the value of field 'noValueColour'. + * + * @return the value of field 'NoValueColour'. + */ + public jalview.binding.types.NoValueColour getNoValueColour() + { + return this._noValueColour; + } + + /** * Returns the value of field 'RGB'. * * @return the value of field 'RGB'. @@ -215,12 +340,11 @@ public class Colour implements java.io.Serializable } /** - * Returns the value of field 'threshType'. The field 'threshType' has the - * following description: loosely specified enumeration: NONE,ABOVE, or BELOW + * Returns the value of field 'threshType'. * * @return the value of field 'ThreshType'. */ - public java.lang.String getThreshType() + public jalview.binding.types.ColourThreshTypeType getThreshType() { return this._threshType; } @@ -360,6 +484,76 @@ public class Colour implements java.io.Serializable } /** + */ + public void removeAllAttributeName() + { + this._attributeNameList.clear(); + } + + /** + * Method removeAttributeName. + * + * @param vAttributeName + * @return true if the object was removed from the collection. + */ + public boolean removeAttributeName(final java.lang.String vAttributeName) + { + boolean removed = _attributeNameList.remove(vAttributeName); + return removed; + } + + /** + * Method removeAttributeNameAt. + * + * @param index + * @return the element removed from the collection + */ + public java.lang.String removeAttributeNameAt(final int index) + { + java.lang.Object obj = this._attributeNameList.remove(index); + return (java.lang.String) obj; + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("setAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + this._attributeNameList.set(index, vAttributeName); + } + + /** + * + * + * @param vAttributeNameArray + */ + public void setAttributeName(final java.lang.String[] vAttributeNameArray) + { + // -- copy array + _attributeNameList.clear(); + + for (int i = 0; i < vAttributeNameArray.length; i++) + { + this._attributeNameList.add(vAttributeNameArray[i]); + } + } + + /** * Sets the value of field 'autoScale'. * * @param autoScale @@ -419,7 +613,9 @@ public class Colour implements java.io.Serializable } /** - * Sets the value of field 'name'. + * Sets the value of field 'name'. The field 'name' has the following + * description: Single letter residue code for an alignment colour scheme, or + * feature type for a feature colour scheme * * @param name * the value of field 'name'. @@ -430,6 +626,18 @@ public class Colour implements java.io.Serializable } /** + * Sets the value of field 'noValueColour'. + * + * @param noValueColour + * the value of field 'noValueColour'. + */ + public void setNoValueColour( + final jalview.binding.types.NoValueColour noValueColour) + { + this._noValueColour = noValueColour; + } + + /** * Sets the value of field 'RGB'. * * @param RGB @@ -441,13 +649,13 @@ public class Colour implements java.io.Serializable } /** - * Sets the value of field 'threshType'. The field 'threshType' has the - * following description: loosely specified enumeration: NONE,ABOVE, or BELOW + * Sets the value of field 'threshType'. * * @param threshType * the value of field 'threshType'. */ - public void setThreshType(final java.lang.String threshType) + public void setThreshType( + final jalview.binding.types.ColourThreshTypeType threshType) { this._threshType = threshType; } diff --git a/src/jalview/binding/CompoundMatcher.java b/src/jalview/binding/CompoundMatcher.java new file mode 100644 index 0000000..a2d1048 --- /dev/null +++ b/src/jalview/binding/CompoundMatcher.java @@ -0,0 +1,368 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class CompoundMatcher. + * + * @version $Revision$ $Date$ + */ +public class CompoundMatcher implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * If true, matchers are AND-ed, if false they are OR-ed + */ + private boolean _and; + + /** + * keeps track of state for field: _and + */ + private boolean _has_and; + + /** + * Field _matcherSetList. + */ + private java.util.Vector _matcherSetList; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public CompoundMatcher() + { + super(); + this._matcherSetList = new java.util.Vector(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * + * + * @param vMatcherSet + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addMatcherSet(final jalview.binding.MatcherSet vMatcherSet) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._matcherSetList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addMatcherSet has a maximum of 2"); + } + + this._matcherSetList.addElement(vMatcherSet); + } + + /** + * + * + * @param index + * @param vMatcherSet + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addMatcherSet(final int index, + final jalview.binding.MatcherSet vMatcherSet) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._matcherSetList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addMatcherSet has a maximum of 2"); + } + + this._matcherSetList.add(index, vMatcherSet); + } + + /** + */ + public void deleteAnd() + { + this._has_and = false; + } + + /** + * Method enumerateMatcherSet. + * + * @return an Enumeration over all jalview.binding.MatcherSet elements + */ + public java.util.Enumeration enumerateMatcherSet() + { + return this._matcherSetList.elements(); + } + + /** + * Returns the value of field 'and'. The field 'and' has the following + * description: If true, matchers are AND-ed, if false they are OR-ed + * + * @return the value of field 'And'. + */ + public boolean getAnd() + { + return this._and; + } + + /** + * Method getMatcherSet. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the jalview.binding.MatcherSet at the given index + */ + public jalview.binding.MatcherSet getMatcherSet(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._matcherSetList.size()) + { + throw new IndexOutOfBoundsException( + "getMatcherSet: Index value '" + index + "' not in range [0.." + + (this._matcherSetList.size() - 1) + "]"); + } + + return (jalview.binding.MatcherSet) _matcherSetList.get(index); + } + + /** + * Method getMatcherSet.Returns the contents of the collection in an Array. + *

          + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public jalview.binding.MatcherSet[] getMatcherSet() + { + jalview.binding.MatcherSet[] array = new jalview.binding.MatcherSet[0]; + return (jalview.binding.MatcherSet[]) this._matcherSetList + .toArray(array); + } + + /** + * Method getMatcherSetCount. + * + * @return the size of this collection + */ + public int getMatcherSetCount() + { + return this._matcherSetList.size(); + } + + /** + * Method hasAnd. + * + * @return true if at least one And has been added + */ + public boolean hasAnd() + { + return this._has_and; + } + + /** + * Returns the value of field 'and'. The field 'and' has the following + * description: If true, matchers are AND-ed, if false they are OR-ed + * + * @return the value of field 'And'. + */ + public boolean isAnd() + { + return this._and; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + */ + public void removeAllMatcherSet() + { + this._matcherSetList.clear(); + } + + /** + * Method removeMatcherSet. + * + * @param vMatcherSet + * @return true if the object was removed from the collection. + */ + public boolean removeMatcherSet( + final jalview.binding.MatcherSet vMatcherSet) + { + boolean removed = _matcherSetList.remove(vMatcherSet); + return removed; + } + + /** + * Method removeMatcherSetAt. + * + * @param index + * @return the element removed from the collection + */ + public jalview.binding.MatcherSet removeMatcherSetAt(final int index) + { + java.lang.Object obj = this._matcherSetList.remove(index); + return (jalview.binding.MatcherSet) obj; + } + + /** + * Sets the value of field 'and'. The field 'and' has the following + * description: If true, matchers are AND-ed, if false they are OR-ed + * + * @param and + * the value of field 'and'. + */ + public void setAnd(final boolean and) + { + this._and = and; + this._has_and = true; + } + + /** + * + * + * @param index + * @param vMatcherSet + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setMatcherSet(final int index, + final jalview.binding.MatcherSet vMatcherSet) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._matcherSetList.size()) + { + throw new IndexOutOfBoundsException( + "setMatcherSet: Index value '" + index + "' not in range [0.." + + (this._matcherSetList.size() - 1) + "]"); + } + + this._matcherSetList.set(index, vMatcherSet); + } + + /** + * + * + * @param vMatcherSetArray + */ + public void setMatcherSet( + final jalview.binding.MatcherSet[] vMatcherSetArray) + { + // -- copy array + _matcherSetList.clear(); + + for (int i = 0; i < vMatcherSetArray.length; i++) + { + this._matcherSetList.add(vMatcherSetArray[i]); + } + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.binding.CompoundMatcher + */ + public static jalview.binding.CompoundMatcher unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.binding.CompoundMatcher) Unmarshaller + .unmarshal(jalview.binding.CompoundMatcher.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/binding/FeatureMatcher.java b/src/jalview/binding/FeatureMatcher.java new file mode 100644 index 0000000..e4e52fb --- /dev/null +++ b/src/jalview/binding/FeatureMatcher.java @@ -0,0 +1,381 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class FeatureMatcher. + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcher implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _by. + */ + private jalview.binding.types.FeatureMatcherByType _by; + + /** + * name of feature attribute to filter on, or attribute and sub-attribute + */ + private java.util.Vector _attributeNameList; + + /** + * Field _condition. + */ + private java.lang.String _condition; + + /** + * Field _value. + */ + private java.lang.String _value; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public FeatureMatcher() + { + super(); + this._attributeNameList = new java.util.Vector(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * + * + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.addElement(vAttributeName); + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.add(index, vAttributeName); + } + + /** + * Method enumerateAttributeName. + * + * @return an Enumeration over all java.lang.String elements + */ + public java.util.Enumeration enumerateAttributeName() + { + return this._attributeNameList.elements(); + } + + /** + * Method getAttributeName. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the java.lang.String at the given index + */ + public java.lang.String getAttributeName(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("getAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + return (java.lang.String) _attributeNameList.get(index); + } + + /** + * Method getAttributeName.Returns the contents of the collection in an Array. + *

          + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public java.lang.String[] getAttributeName() + { + java.lang.String[] array = new java.lang.String[0]; + return (java.lang.String[]) this._attributeNameList.toArray(array); + } + + /** + * Method getAttributeNameCount. + * + * @return the size of this collection + */ + public int getAttributeNameCount() + { + return this._attributeNameList.size(); + } + + /** + * Returns the value of field 'by'. + * + * @return the value of field 'By'. + */ + public jalview.binding.types.FeatureMatcherByType getBy() + { + return this._by; + } + + /** + * Returns the value of field 'condition'. + * + * @return the value of field 'Condition'. + */ + public java.lang.String getCondition() + { + return this._condition; + } + + /** + * Returns the value of field 'value'. + * + * @return the value of field 'Value'. + */ + public java.lang.String getValue() + { + return this._value; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + */ + public void removeAllAttributeName() + { + this._attributeNameList.clear(); + } + + /** + * Method removeAttributeName. + * + * @param vAttributeName + * @return true if the object was removed from the collection. + */ + public boolean removeAttributeName(final java.lang.String vAttributeName) + { + boolean removed = _attributeNameList.remove(vAttributeName); + return removed; + } + + /** + * Method removeAttributeNameAt. + * + * @param index + * @return the element removed from the collection + */ + public java.lang.String removeAttributeNameAt(final int index) + { + java.lang.Object obj = this._attributeNameList.remove(index); + return (java.lang.String) obj; + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("setAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + this._attributeNameList.set(index, vAttributeName); + } + + /** + * + * + * @param vAttributeNameArray + */ + public void setAttributeName(final java.lang.String[] vAttributeNameArray) + { + // -- copy array + _attributeNameList.clear(); + + for (int i = 0; i < vAttributeNameArray.length; i++) + { + this._attributeNameList.add(vAttributeNameArray[i]); + } + } + + /** + * Sets the value of field 'by'. + * + * @param by + * the value of field 'by'. + */ + public void setBy(final jalview.binding.types.FeatureMatcherByType by) + { + this._by = by; + } + + /** + * Sets the value of field 'condition'. + * + * @param condition + * the value of field 'condition'. + */ + public void setCondition(final java.lang.String condition) + { + this._condition = condition; + } + + /** + * Sets the value of field 'value'. + * + * @param value + * the value of field 'value'. + */ + public void setValue(final java.lang.String value) + { + this._value = value; + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.binding.FeatureMatcher + */ + public static jalview.binding.FeatureMatcher unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.binding.FeatureMatcher) Unmarshaller + .unmarshal(jalview.binding.FeatureMatcher.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/binding/FeatureMatcherSet.java b/src/jalview/binding/FeatureMatcherSet.java new file mode 100644 index 0000000..7ba5f0e --- /dev/null +++ b/src/jalview/binding/FeatureMatcherSet.java @@ -0,0 +1,200 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * A feature match condition, which may be simple or compound + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcherSet implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Internal choice value storage + */ + private java.lang.Object _choiceValue; + + /** + * Field _matchCondition. + */ + private jalview.binding.MatchCondition _matchCondition; + + /** + * Field _compoundMatcher. + */ + private jalview.binding.CompoundMatcher _compoundMatcher; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public FeatureMatcherSet() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Returns the value of field 'choiceValue'. The field 'choiceValue' has the + * following description: Internal choice value storage + * + * @return the value of field 'ChoiceValue'. + */ + public java.lang.Object getChoiceValue() + { + return this._choiceValue; + } + + /** + * Returns the value of field 'compoundMatcher'. + * + * @return the value of field 'CompoundMatcher'. + */ + public jalview.binding.CompoundMatcher getCompoundMatcher() + { + return this._compoundMatcher; + } + + /** + * Returns the value of field 'matchCondition'. + * + * @return the value of field 'MatchCondition'. + */ + public jalview.binding.MatchCondition getMatchCondition() + { + return this._matchCondition; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Sets the value of field 'compoundMatcher'. + * + * @param compoundMatcher + * the value of field 'compoundMatcher'. + */ + public void setCompoundMatcher( + final jalview.binding.CompoundMatcher compoundMatcher) + { + this._compoundMatcher = compoundMatcher; + this._choiceValue = compoundMatcher; + } + + /** + * Sets the value of field 'matchCondition'. + * + * @param matchCondition + * the value of field 'matchCondition'. + */ + public void setMatchCondition( + final jalview.binding.MatchCondition matchCondition) + { + this._matchCondition = matchCondition; + this._choiceValue = matchCondition; + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.binding.FeatureMatcherSet + */ + public static jalview.binding.FeatureMatcherSet unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.binding.FeatureMatcherSet) Unmarshaller + .unmarshal(jalview.binding.FeatureMatcherSet.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/binding/Filter.java b/src/jalview/binding/Filter.java new file mode 100644 index 0000000..687ae91 --- /dev/null +++ b/src/jalview/binding/Filter.java @@ -0,0 +1,180 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class Filter. + * + * @version $Revision$ $Date$ + */ +public class Filter implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _featureType. + */ + private java.lang.String _featureType; + + /** + * Field _matcherSet. + */ + private jalview.binding.MatcherSet _matcherSet; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public Filter() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Returns the value of field 'featureType'. + * + * @return the value of field 'FeatureType'. + */ + public java.lang.String getFeatureType() + { + return this._featureType; + } + + /** + * Returns the value of field 'matcherSet'. + * + * @return the value of field 'MatcherSet'. + */ + public jalview.binding.MatcherSet getMatcherSet() + { + return this._matcherSet; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Sets the value of field 'featureType'. + * + * @param featureType + * the value of field 'featureType'. + */ + public void setFeatureType(final java.lang.String featureType) + { + this._featureType = featureType; + } + + /** + * Sets the value of field 'matcherSet'. + * + * @param matcherSet + * the value of field 'matcherSet'. + */ + public void setMatcherSet(final jalview.binding.MatcherSet matcherSet) + { + this._matcherSet = matcherSet; + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.binding.Filter + */ + public static jalview.binding.Filter unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.binding.Filter) Unmarshaller + .unmarshal(jalview.binding.Filter.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/binding/JalviewUserColours.java b/src/jalview/binding/JalviewUserColours.java index 6709487..67ee5a2 100644 --- a/src/jalview/binding/JalviewUserColours.java +++ b/src/jalview/binding/JalviewUserColours.java @@ -42,6 +42,11 @@ public class JalviewUserColours implements java.io.Serializable */ private java.util.Vector _colourList; + /** + * Field _filterList. + */ + private java.util.Vector _filterList; + // ----------------/ // - Constructors -/ // ----------------/ @@ -50,6 +55,7 @@ public class JalviewUserColours implements java.io.Serializable { super(); this._colourList = new java.util.Vector(); + this._filterList = new java.util.Vector(); } // -----------/ @@ -84,6 +90,33 @@ public class JalviewUserColours implements java.io.Serializable } /** + * + * + * @param vFilter + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addFilter(final Filter vFilter) + throws java.lang.IndexOutOfBoundsException + { + this._filterList.addElement(vFilter); + } + + /** + * + * + * @param index + * @param vFilter + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addFilter(final int index, final Filter vFilter) + throws java.lang.IndexOutOfBoundsException + { + this._filterList.add(index, vFilter); + } + + /** * Method enumerateColour. * * @return an Enumeration over all Colour elements @@ -94,6 +127,16 @@ public class JalviewUserColours implements java.io.Serializable } /** + * Method enumerateFilter. + * + * @return an Enumeration over all Filter elements + */ + public java.util.Enumeration enumerateFilter() + { + return this._filterList.elements(); + } + + /** * Method getColour. * * @param index @@ -141,6 +184,53 @@ public class JalviewUserColours implements java.io.Serializable } /** + * Method getFilter. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the Filter at the given index + */ + public Filter getFilter(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._filterList.size()) + { + throw new IndexOutOfBoundsException( + "getFilter: Index value '" + index + "' not in range [0.." + + (this._filterList.size() - 1) + "]"); + } + + return (Filter) _filterList.get(index); + } + + /** + * Method getFilter.Returns the contents of the collection in an Array. + *

          + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public Filter[] getFilter() + { + Filter[] array = new Filter[0]; + return (Filter[]) this._filterList.toArray(array); + } + + /** + * Method getFilterCount. + * + * @return the size of this collection + */ + public int getFilterCount() + { + return this._filterList.size(); + } + + /** * Returns the value of field 'schemeName'. * * @return the value of field 'SchemeName'. @@ -217,13 +307,20 @@ public class JalviewUserColours implements java.io.Serializable } /** - */ + */ public void removeAllColour() { this._colourList.clear(); } /** + */ + public void removeAllFilter() + { + this._filterList.clear(); + } + + /** * Method removeColour. * * @param vColour @@ -248,6 +345,30 @@ public class JalviewUserColours implements java.io.Serializable } /** + * Method removeFilter. + * + * @param vFilter + * @return true if the object was removed from the collection. + */ + public boolean removeFilter(final Filter vFilter) + { + boolean removed = _filterList.remove(vFilter); + return removed; + } + + /** + * Method removeFilterAt. + * + * @param index + * @return the element removed from the collection + */ + public Filter removeFilterAt(final int index) + { + java.lang.Object obj = this._filterList.remove(index); + return (Filter) obj; + } + + /** * * * @param index @@ -286,6 +407,44 @@ public class JalviewUserColours implements java.io.Serializable } /** + * + * + * @param index + * @param vFilter + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setFilter(final int index, final Filter vFilter) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._filterList.size()) + { + throw new IndexOutOfBoundsException( + "setFilter: Index value '" + index + "' not in range [0.." + + (this._filterList.size() - 1) + "]"); + } + + this._filterList.set(index, vFilter); + } + + /** + * + * + * @param vFilterArray + */ + public void setFilter(final Filter[] vFilterArray) + { + // -- copy array + _filterList.clear(); + + for (int i = 0; i < vFilterArray.length; i++) + { + this._filterList.add(vFilterArray[i]); + } + } + + /** * Sets the value of field 'schemeName'. * * @param schemeName diff --git a/src/jalview/binding/MatchCondition.java b/src/jalview/binding/MatchCondition.java new file mode 100644 index 0000000..44a3d3e --- /dev/null +++ b/src/jalview/binding/MatchCondition.java @@ -0,0 +1,125 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class MatchCondition. + * + * @version $Revision$ $Date$ + */ +public class MatchCondition extends FeatureMatcher + implements java.io.Serializable +{ + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public MatchCondition() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.binding.FeatureMatcher + */ + public static jalview.binding.FeatureMatcher unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.binding.FeatureMatcher) Unmarshaller + .unmarshal(jalview.binding.MatchCondition.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/binding/MatcherSet.java b/src/jalview/binding/MatcherSet.java new file mode 100644 index 0000000..756d93a --- /dev/null +++ b/src/jalview/binding/MatcherSet.java @@ -0,0 +1,125 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class MatcherSet. + * + * @version $Revision$ $Date$ + */ +public class MatcherSet extends FeatureMatcherSet + implements java.io.Serializable +{ + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public MatcherSet() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.binding.FeatureMatcherSet + */ + public static jalview.binding.FeatureMatcherSet unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.binding.FeatureMatcherSet) Unmarshaller + .unmarshal(jalview.binding.MatcherSet.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/binding/types/ColourThreshTypeType.java b/src/jalview/binding/types/ColourThreshTypeType.java new file mode 100644 index 0000000..024f2c0 --- /dev/null +++ b/src/jalview/binding/types/ColourThreshTypeType.java @@ -0,0 +1,168 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding.types; + + //---------------------------------/ + //- Imported classes and packages -/ +//---------------------------------/ + +import java.util.Hashtable; + +/** + * Class ColourThreshTypeType. + * + * @version $Revision$ $Date$ + */ +public class ColourThreshTypeType implements java.io.Serializable { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * The NONE type + */ + public static final int NONE_TYPE = 0; + + /** + * The instance of the NONE type + */ + public static final ColourThreshTypeType NONE = new ColourThreshTypeType(NONE_TYPE, "NONE"); + + /** + * The ABOVE type + */ + public static final int ABOVE_TYPE = 1; + + /** + * The instance of the ABOVE type + */ + public static final ColourThreshTypeType ABOVE = new ColourThreshTypeType(ABOVE_TYPE, "ABOVE"); + + /** + * The BELOW type + */ + public static final int BELOW_TYPE = 2; + + /** + * The instance of the BELOW type + */ + public static final ColourThreshTypeType BELOW = new ColourThreshTypeType(BELOW_TYPE, "BELOW"); + + /** + * Field _memberTable. + */ + private static java.util.Hashtable _memberTable = init(); + + /** + * Field type. + */ + private int type = -1; + + /** + * Field stringValue. + */ + private java.lang.String stringValue = null; + + + //----------------/ + //- Constructors -/ + //----------------/ + + private ColourThreshTypeType(final int type, final java.lang.String value) { + super(); + this.type = type; + this.stringValue = value; + } + + + //-----------/ + //- Methods -/ + //-----------/ + + /** + * Method enumerate.Returns an enumeration of all possible + * instances of ColourThreshTypeType + * + * @return an Enumeration over all possible instances of + * ColourThreshTypeType + */ + public static java.util.Enumeration enumerate( + ) { + return _memberTable.elements(); + } + + /** + * Method getType.Returns the type of this ColourThreshTypeType + * + * @return the type of this ColourThreshTypeType + */ + public int getType( + ) { + return this.type; + } + + /** + * Method init. + * + * @return the initialized Hashtable for the member table + */ + private static java.util.Hashtable init( + ) { + Hashtable members = new Hashtable(); + members.put("NONE", NONE); + members.put("ABOVE", ABOVE); + members.put("BELOW", BELOW); + return members; + } + + /** + * Method readResolve. will be called during deserialization to + * replace the deserialized object with the correct constant + * instance. + * + * @return this deserialized object + */ + private java.lang.Object readResolve( + ) { + return valueOf(this.stringValue); + } + + /** + * Method toString.Returns the String representation of this + * ColourThreshTypeType + * + * @return the String representation of this ColourThreshTypeTyp + */ + public java.lang.String toString( + ) { + return this.stringValue; + } + + /** + * Method valueOf.Returns a new ColourThreshTypeType based on + * the given String value. + * + * @param string + * @return the ColourThreshTypeType value of parameter 'string' + */ + public static jalview.binding.types.ColourThreshTypeType valueOf( + final java.lang.String string) { + java.lang.Object obj = null; + if (string != null) { + obj = _memberTable.get(string); + } + if (obj == null) { + String err = "" + string + " is not a valid ColourThreshTypeType"; + throw new IllegalArgumentException(err); + } + return (ColourThreshTypeType) obj; + } + +} diff --git a/src/jalview/binding/types/FeatureMatcherByType.java b/src/jalview/binding/types/FeatureMatcherByType.java new file mode 100644 index 0000000..2185bba --- /dev/null +++ b/src/jalview/binding/types/FeatureMatcherByType.java @@ -0,0 +1,168 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding.types; + + //---------------------------------/ + //- Imported classes and packages -/ +//---------------------------------/ + +import java.util.Hashtable; + +/** + * Class FeatureMatcherByType. + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcherByType implements java.io.Serializable { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * The byLabel type + */ + public static final int BYLABEL_TYPE = 0; + + /** + * The instance of the byLabel type + */ + public static final FeatureMatcherByType BYLABEL = new FeatureMatcherByType(BYLABEL_TYPE, "byLabel"); + + /** + * The byScore type + */ + public static final int BYSCORE_TYPE = 1; + + /** + * The instance of the byScore type + */ + public static final FeatureMatcherByType BYSCORE = new FeatureMatcherByType(BYSCORE_TYPE, "byScore"); + + /** + * The byAttribute type + */ + public static final int BYATTRIBUTE_TYPE = 2; + + /** + * The instance of the byAttribute type + */ + public static final FeatureMatcherByType BYATTRIBUTE = new FeatureMatcherByType(BYATTRIBUTE_TYPE, "byAttribute"); + + /** + * Field _memberTable. + */ + private static java.util.Hashtable _memberTable = init(); + + /** + * Field type. + */ + private int type = -1; + + /** + * Field stringValue. + */ + private java.lang.String stringValue = null; + + + //----------------/ + //- Constructors -/ + //----------------/ + + private FeatureMatcherByType(final int type, final java.lang.String value) { + super(); + this.type = type; + this.stringValue = value; + } + + + //-----------/ + //- Methods -/ + //-----------/ + + /** + * Method enumerate.Returns an enumeration of all possible + * instances of FeatureMatcherByType + * + * @return an Enumeration over all possible instances of + * FeatureMatcherByType + */ + public static java.util.Enumeration enumerate( + ) { + return _memberTable.elements(); + } + + /** + * Method getType.Returns the type of this FeatureMatcherByType + * + * @return the type of this FeatureMatcherByType + */ + public int getType( + ) { + return this.type; + } + + /** + * Method init. + * + * @return the initialized Hashtable for the member table + */ + private static java.util.Hashtable init( + ) { + Hashtable members = new Hashtable(); + members.put("byLabel", BYLABEL); + members.put("byScore", BYSCORE); + members.put("byAttribute", BYATTRIBUTE); + return members; + } + + /** + * Method readResolve. will be called during deserialization to + * replace the deserialized object with the correct constant + * instance. + * + * @return this deserialized object + */ + private java.lang.Object readResolve( + ) { + return valueOf(this.stringValue); + } + + /** + * Method toString.Returns the String representation of this + * FeatureMatcherByType + * + * @return the String representation of this FeatureMatcherByTyp + */ + public java.lang.String toString( + ) { + return this.stringValue; + } + + /** + * Method valueOf.Returns a new FeatureMatcherByType based on + * the given String value. + * + * @param string + * @return the FeatureMatcherByType value of parameter 'string' + */ + public static jalview.binding.types.FeatureMatcherByType valueOf( + final java.lang.String string) { + java.lang.Object obj = null; + if (string != null) { + obj = _memberTable.get(string); + } + if (obj == null) { + String err = "" + string + " is not a valid FeatureMatcherByType"; + throw new IllegalArgumentException(err); + } + return (FeatureMatcherByType) obj; + } + +} diff --git a/src/jalview/binding/types/NoValueColour.java b/src/jalview/binding/types/NoValueColour.java new file mode 100644 index 0000000..c1540f6 --- /dev/null +++ b/src/jalview/binding/types/NoValueColour.java @@ -0,0 +1,169 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.binding.types; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import java.util.Hashtable; + +/** + * Graduated feature colour if no score (or attribute) value + * + * @version $Revision$ $Date$ + */ +public class NoValueColour implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * The None type + */ + public static final int NONE_TYPE = 0; + + /** + * The instance of the None type + */ + public static final NoValueColour NONE = new NoValueColour(NONE_TYPE, + "None"); + + /** + * The Min type + */ + public static final int MIN_TYPE = 1; + + /** + * The instance of the Min type + */ + public static final NoValueColour MIN = new NoValueColour(MIN_TYPE, + "Min"); + + /** + * The Max type + */ + public static final int MAX_TYPE = 2; + + /** + * The instance of the Max type + */ + public static final NoValueColour MAX = new NoValueColour(MAX_TYPE, + "Max"); + + /** + * Field _memberTable. + */ + private static java.util.Hashtable _memberTable = init(); + + /** + * Field type. + */ + private int type = -1; + + /** + * Field stringValue. + */ + private java.lang.String stringValue = null; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + private NoValueColour(final int type, final java.lang.String value) + { + super(); + this.type = type; + this.stringValue = value; + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method enumerate.Returns an enumeration of all possible instances of + * NoValueColour + * + * @return an Enumeration over all possible instances of NoValueColour + */ + public static java.util.Enumeration enumerate() + { + return _memberTable.elements(); + } + + /** + * Method getType.Returns the type of this NoValueColour + * + * @return the type of this NoValueColour + */ + public int getType() + { + return this.type; + } + + /** + * Method init. + * + * @return the initialized Hashtable for the member table + */ + private static java.util.Hashtable init() + { + Hashtable members = new Hashtable(); + members.put("None", NONE); + members.put("Min", MIN); + members.put("Max", MAX); + return members; + } + + /** + * Method readResolve. will be called during deserialization to replace the + * deserialized object with the correct constant instance. + * + * @return this deserialized object + */ + private java.lang.Object readResolve() + { + return valueOf(this.stringValue); + } + + /** + * Method toString.Returns the String representation of this NoValueColour + * + * @return the String representation of this NoValueColour + */ + public java.lang.String toString() + { + return this.stringValue; + } + + /** + * Method valueOf.Returns a new NoValueColour based on the given String value. + * + * @param string + * @return the NoValueColour value of parameter 'string' + */ + public static jalview.binding.types.NoValueColour valueOf( + final java.lang.String string) + { + java.lang.Object obj = null; + if (string != null) + { + obj = _memberTable.get(string); + } + if (obj == null) + { + String err = "" + string + " is not a valid NoValueColour"; + throw new IllegalArgumentException(err); + } + return (NoValueColour) obj; + } + +} diff --git a/src/jalview/controller/AlignViewController.java b/src/jalview/controller/AlignViewController.java index 460c2b3..d992e4e 100644 --- a/src/jalview/controller/AlignViewController.java +++ b/src/jalview/controller/AlignViewController.java @@ -53,20 +53,19 @@ public class AlignViewController implements AlignViewControllerI private AlignViewControllerGuiI avcg; public AlignViewController(AlignViewControllerGuiI alignFrame, - AlignViewportI viewport, AlignmentViewPanel alignPanel) + AlignViewportI vp, AlignmentViewPanel ap) { this.avcg = alignFrame; - this.viewport = viewport; - this.alignPanel = alignPanel; + this.viewport = vp; + this.alignPanel = ap; } @Override - public void setViewportAndAlignmentPanel(AlignViewportI viewport, - AlignmentViewPanel alignPanel) + public void setViewportAndAlignmentPanel(AlignViewportI vp, + AlignmentViewPanel ap) { - this.alignPanel = alignPanel; - this.viewport = viewport; - + this.alignPanel = ap; + this.viewport = vp; } @Override @@ -215,17 +214,21 @@ public class AlignViewController implements AlignViewControllerI /** * Sets a bit in the BitSet for each column (base 0) in the sequence - * collection which includes the specified feature type. Returns the number of - * sequences which have the feature in the selected range. + * collection which includes a visible feature of the specified feature type. + * Returns the number of sequences which have the feature visible in the + * selected range. * * @param featureType * @param sqcol * @param bs * @return */ - static int findColumnsWithFeature(String featureType, + int findColumnsWithFeature(String featureType, SequenceCollectionI sqcol, BitSet bs) { + FeatureRenderer fr = alignPanel == null ? null : alignPanel + .getFeatureRenderer(); + final int startColumn = sqcol.getStartRes() + 1; // converted to base 1 final int endColumn = sqcol.getEndRes() + 1; List seqs = sqcol.getSequences(); @@ -238,13 +241,19 @@ public class AlignViewController implements AlignViewControllerI List sfs = sq.findFeatures(startColumn, endColumn, featureType); - if (!sfs.isEmpty()) - { - nseq++; - } - + boolean found = false; for (SequenceFeature sf : sfs) { + if (fr.getColour(sf) == null) + { + continue; + } + if (!found) + { + nseq++; + } + found = true; + int sfStartCol = sq.findIndex(sf.getBegin()); int sfEndCol = sq.findIndex(sf.getEnd()); @@ -343,25 +352,25 @@ public class AlignViewController implements AlignViewControllerI public boolean parseFeaturesFile(String file, DataSourceType protocol, boolean relaxedIdMatching) { - boolean featuresFile = false; + boolean featuresAdded = false; + FeatureRenderer fr = alignPanel.getFeatureRenderer(); try { - featuresFile = new FeaturesFile(false, file, protocol).parse( - viewport.getAlignment().getDataset(), - alignPanel.getFeatureRenderer().getFeatureColours(), false, - relaxedIdMatching); + featuresAdded = new FeaturesFile(false, file, protocol).parse( + viewport.getAlignment().getDataset(), fr.getFeatureColours(), + fr.getFeatureFilters(), false, relaxedIdMatching); } catch (Exception ex) { ex.printStackTrace(); } - if (featuresFile) + if (featuresAdded) { avcg.refreshFeatureUI(true); - if (alignPanel.getFeatureRenderer() != null) + if (fr != null) { // update the min/max ranges where necessary - alignPanel.getFeatureRenderer().findAllFeatures(true); + fr.findAllFeatures(true); } if (avcg.getFeatureSettingsUI() != null) { @@ -370,7 +379,7 @@ public class AlignViewController implements AlignViewControllerI alignPanel.paintAlignment(true, true); } - return featuresFile; + return featuresAdded; } diff --git a/src/jalview/datamodel/Alignment.java b/src/jalview/datamodel/Alignment.java index f268d37..3ba35b6 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -29,10 +29,12 @@ import jalview.util.MessageManager; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -49,7 +51,7 @@ public class Alignment implements AlignmentI { private Alignment dataset; - protected List sequences; + private List sequences; protected List groups; @@ -198,6 +200,7 @@ public class Alignment implements AlignmentI return sequences.get(i); } } + return null; } @@ -706,7 +709,7 @@ public class Alignment implements AlignmentI public int getWidth() { int maxLength = -1; - + for (int i = 0; i < sequences.size(); i++) { if (getSequenceAt(i).getLength() > maxLength) @@ -714,9 +717,34 @@ public class Alignment implements AlignmentI maxLength = getSequenceAt(i).getLength(); } } - + return maxLength; } + /* + @Override + public int getWidth() + { + final Wrapper temp = new Wrapper(); + + forEachSequence(new Consumer() + { + @Override + public void accept(SequenceI s) + { + if (s.getLength() > temp.inner) + { + temp.inner = s.getLength(); + } + } + }, 0, sequences.size() - 1); + + return temp.inner; + } + + public static class Wrapper + { + public int inner; + }*/ /** * DOCUMENT ME! @@ -1603,7 +1631,10 @@ public class Alignment implements AlignmentI AlignmentAnnotation annot = new AlignmentAnnotation(name, name, new Annotation[1], 0f, 0f, AlignmentAnnotation.BAR_GRAPH); annot.hasText = false; - annot.setCalcId(new String(calcId)); + if (calcId != null) + { + annot.setCalcId(new String(calcId)); + } annot.autoCalculated = autoCalc; if (seqRef != null) { @@ -1895,4 +1926,117 @@ public class Alignment implements AlignmentI { hiddenCols = cols; } + + @Override + public void setupJPredAlignment() + { + SequenceI repseq = getSequenceAt(0); + setSeqrep(repseq); + HiddenColumns cs = new HiddenColumns(); + cs.hideList(repseq.getInsertions()); + setHiddenColumns(cs); + } + + @Override + public HiddenColumns propagateInsertions(SequenceI profileseq, + AlignmentView input) + { + int profsqpos = 0; + + char gc = getGapCharacter(); + Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc); + HiddenColumns nview = (HiddenColumns) alandhidden[1]; + SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos]; + return propagateInsertions(profileseq, origseq, nview); + } + + /** + * + * @param profileseq + * sequence in al which corresponds to origseq + * @param al + * alignment which is to have gaps inserted into it + * @param origseq + * sequence corresponding to profileseq which defines gap map for + * modifying al + */ + private HiddenColumns propagateInsertions(SequenceI profileseq, + SequenceI origseq, HiddenColumns hc) + { + // take the set of hidden columns, and the set of gaps in origseq, + // and remove all the hidden gaps from hiddenColumns + + // first get the gaps as a Bitset + // then calculate hidden ^ not(gap) + BitSet gaps = origseq.gapBitset(); + hc.andNot(gaps); + + // for each sequence in the alignment, except the profile sequence, + // insert gaps corresponding to each hidden region but where each hidden + // column region is shifted backwards by the number of preceding visible + // gaps update hidden columns at the same time + HiddenColumns newhidden = new HiddenColumns(); + + int numGapsBefore = 0; + int gapPosition = 0; + Iterator it = hc.iterator(); + while (it.hasNext()) + { + int[] region = it.next(); + + // get region coordinates accounting for gaps + // we can rely on gaps not being *in* hidden regions because we already + // removed those + while (gapPosition < region[0]) + { + gapPosition++; + if (gaps.get(gapPosition)) + { + numGapsBefore++; + } + } + + int left = region[0] - numGapsBefore; + int right = region[1] - numGapsBefore; + + newhidden.hideColumns(left, right); + padGaps(left, right, profileseq); + } + return newhidden; + } + + /** + * Pad gaps in all sequences in alignment except profileseq + * + * @param left + * position of first gap to insert + * @param right + * position of last gap to insert + * @param profileseq + * sequence not to pad + */ + private void padGaps(int left, int right, SequenceI profileseq) + { + char gc = getGapCharacter(); + + // make a string with number of gaps = length of hidden region + StringBuilder sb = new StringBuilder(); + for (int g = 0; g < right - left + 1; g++) + { + sb.append(gc); + } + + // loop over the sequences and pad with gaps where required + for (int s = 0, ns = getHeight(); s < ns; s++) + { + SequenceI sqobj = getSequenceAt(s); + if ((sqobj != profileseq) && (sqobj.getLength() >= left)) + { + String sq = sqobj.getSequenceAsString(); + sqobj.setSequence( + sq.substring(0, left) + sb.toString() + sq.substring(left)); + } + } + } + } diff --git a/src/jalview/datamodel/AlignmentAnnotation.java b/src/jalview/datamodel/AlignmentAnnotation.java index f7bf4d8..ee9389c 100755 --- a/src/jalview/datamodel/AlignmentAnnotation.java +++ b/src/jalview/datamodel/AlignmentAnnotation.java @@ -25,6 +25,7 @@ import jalview.analysis.SecStrConsensus.SimpleBP; import jalview.analysis.WUSSParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -164,6 +165,64 @@ public class AlignmentAnnotation } /** + * Get the RNA Secondary Structure SequenceFeature Array if present + */ + public SequenceFeature[] getRnaSecondaryStructure() + { + return this._rnasecstr; + } + + /** + * Check the RNA Secondary Structure is equivalent to one in given + * AlignmentAnnotation param + */ + public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that) + { + return rnaSecondaryStructureEquivalent(that, true); + } + + public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that, boolean compareType) + { + SequenceFeature[] thisSfArray = this.getRnaSecondaryStructure(); + SequenceFeature[] thatSfArray = that.getRnaSecondaryStructure(); + if (thisSfArray == null || thatSfArray == null) + { + return thisSfArray == null && thatSfArray == null; + } + if (thisSfArray.length != thatSfArray.length) + { + return false; + } + Arrays.sort(thisSfArray, new SFSortByEnd()); // probably already sorted + // like this + Arrays.sort(thatSfArray, new SFSortByEnd()); // probably already sorted + // like this + for (int i=0; i < thisSfArray.length; i++) { + SequenceFeature thisSf = thisSfArray[i]; + SequenceFeature thatSf = thatSfArray[i]; + if (compareType) { + if (thisSf.getType() == null || thatSf.getType() == null) { + if (thisSf.getType() == null && thatSf.getType() == null) { + continue; + } else { + return false; + } + } + if (! thisSf.getType().equals(thatSf.getType())) { + return false; + } + } + if (!(thisSf.getBegin() == thatSf.getBegin() + && thisSf.getEnd() == thatSf.getEnd())) + { + return false; + } + } + return true; + + } + + /** * map of positions in the associated annotation */ private Map sequenceMapping; @@ -294,6 +353,7 @@ public class AlignmentAnnotation char firstChar = 0; for (int i = 0; i < annotations.length; i++) { + // DEBUG System.out.println(i + ": " + annotations[i]); if (annotations[i] == null) { continue; @@ -301,12 +361,15 @@ public class AlignmentAnnotation if (annotations[i].secondaryStructure == 'H' || annotations[i].secondaryStructure == 'E') { + // DEBUG System.out.println( "/H|E/ '" + + // annotations[i].secondaryStructure + "'"); hasIcons |= true; } else // Check for RNA secondary structure { - // System.out.println(annotations[i].secondaryStructure); + // DEBUG System.out.println( "/else/ '" + + // annotations[i].secondaryStructure + "'"); // TODO: 2.8.2 should this ss symbol validation check be a function in // RNA/ResidueProperties ? if (annotations[i].secondaryStructure == '(' @@ -317,10 +380,12 @@ public class AlignmentAnnotation || annotations[i].secondaryStructure == 'B' || annotations[i].secondaryStructure == 'C' || annotations[i].secondaryStructure == 'D' - || annotations[i].secondaryStructure == 'E' + // || annotations[i].secondaryStructure == 'E' // ambiguous on + // its own -- already checked above || annotations[i].secondaryStructure == 'F' || annotations[i].secondaryStructure == 'G' - || annotations[i].secondaryStructure == 'H' + // || annotations[i].secondaryStructure == 'H' // ambiguous on + // its own -- already checked above || annotations[i].secondaryStructure == 'I' || annotations[i].secondaryStructure == 'J' || annotations[i].secondaryStructure == 'K' @@ -367,7 +432,7 @@ public class AlignmentAnnotation // && // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure firstChar != ' ' && firstChar != '$' && firstChar != 0xCE - && firstChar != '(' && firstChar != '[' && firstChar != '>' + && firstChar != '(' && firstChar != '[' && firstChar != '<' && firstChar != '{' && firstChar != 'A' && firstChar != 'B' && firstChar != 'C' && firstChar != 'D' && firstChar != 'E' && firstChar != 'F' && firstChar != 'G' && firstChar != 'H' @@ -666,7 +731,7 @@ public class AlignmentAnnotation this.calcId = annotation.calcId; if (annotation.properties != null) { - properties = new HashMap(); + properties = new HashMap<>(); for (Map.Entry val : annotation.properties.entrySet()) { properties.put(val.getKey(), val.getValue()); @@ -702,7 +767,7 @@ public class AlignmentAnnotation if (annotation.sequenceMapping != null) { Integer p = null; - sequenceMapping = new HashMap(); + sequenceMapping = new HashMap<>(); Iterator pos = annotation.sequenceMapping.keySet() .iterator(); while (pos.hasNext()) @@ -782,7 +847,7 @@ public class AlignmentAnnotation int epos = sequenceRef.findPosition(endRes); if (sequenceMapping != null) { - Map newmapping = new HashMap(); + Map newmapping = new HashMap<>(); Iterator e = sequenceMapping.keySet().iterator(); while (e.hasNext()) { @@ -909,7 +974,7 @@ public class AlignmentAnnotation { return; } - sequenceMapping = new HashMap(); + sequenceMapping = new HashMap<>(); int seqPos; @@ -1124,7 +1189,7 @@ public class AlignmentAnnotation { return; } - hidden.makeVisibleAnnotation(this); + makeVisibleAnnotation(hidden); } public void setPadGaps(boolean padgaps, char gapchar) @@ -1190,7 +1255,7 @@ public class AlignmentAnnotation /** * properties associated with the calcId */ - protected Map properties = new HashMap(); + protected Map properties = new HashMap<>(); /** * base colour for line graphs. If null, will be set automatically by @@ -1236,7 +1301,7 @@ public class AlignmentAnnotation : false; // TODO build a better annotation element map and get rid of annotations[] - Map mapForsq = new HashMap(); + Map mapForsq = new HashMap<>(); if (sequenceMapping != null) { if (sp2sq != null) @@ -1289,7 +1354,7 @@ public class AlignmentAnnotation if (mapping != null) { Map old = sequenceMapping; - Map remap = new HashMap(); + Map remap = new HashMap<>(); int index = -1; for (int mp[] : mapping.values()) { @@ -1347,7 +1412,7 @@ public class AlignmentAnnotation { if (properties == null) { - properties = new HashMap(); + properties = new HashMap<>(); } properties.put(property, value); } @@ -1473,6 +1538,115 @@ public class AlignmentAnnotation return graphMin < graphMax; } + /** + * delete any columns in alignmentAnnotation that are hidden (including + * sequence associated annotation). + * + * @param hiddenColumns + * the set of hidden columns + */ + public void makeVisibleAnnotation(HiddenColumns hiddenColumns) + { + if (annotations != null) + { + makeVisibleAnnotation(0, annotations.length, hiddenColumns); + } + } + + /** + * delete any columns in alignmentAnnotation that are hidden (including + * sequence associated annotation). + * + * @param start + * remove any annotation to the right of this column + * @param end + * remove any annotation to the left of this column + * @param hiddenColumns + * the set of hidden columns + */ + public void makeVisibleAnnotation(int start, int end, + HiddenColumns hiddenColumns) + { + if (annotations != null) + { + if (hiddenColumns.hasHiddenColumns()) + { + removeHiddenAnnotation(start, end, hiddenColumns); + } + else + { + restrict(start, end); + } + } + } + + /** + * The actual implementation of deleting hidden annotation columns + * + * @param start + * remove any annotation to the right of this column + * @param end + * remove any annotation to the left of this column + * @param hiddenColumns + * the set of hidden columns + */ + private void removeHiddenAnnotation(int start, int end, + HiddenColumns hiddenColumns) + { + // mangle the alignmentAnnotation annotation array + ArrayList annels = new ArrayList<>(); + Annotation[] els = null; + + int w = 0; + + Iterator blocks = hiddenColumns.getVisContigsIterator(start, + end + 1, false); + + int copylength; + int annotationLength; + while (blocks.hasNext()) + { + int[] block = blocks.next(); + annotationLength = block[1] - block[0] + 1; + + if (blocks.hasNext()) + { + // copy just the visible segment of the annotation row + copylength = annotationLength; + } + else + { + if (annotationLength + block[0] <= annotations.length) + { + // copy just the visible segment of the annotation row + copylength = annotationLength; + } + else + { + // copy to the end of the annotation row + copylength = annotations.length - block[0]; + } + } + + els = new Annotation[annotationLength]; + annels.add(els); + System.arraycopy(annotations, block[0], els, 0, copylength); + w += annotationLength; + } + + if (w != 0) + { + annotations = new Annotation[w]; + + w = 0; + for (Annotation[] chnk : annels) + { + System.arraycopy(chnk, 0, annotations, w, chnk.length); + w += chnk.length; + } + } + } + public static Iterable findAnnotations( Iterable list, SequenceI seq, String calcId, String label) @@ -1539,4 +1713,5 @@ public class AlignmentAnnotation } return aa; } + } diff --git a/src/jalview/datamodel/AlignmentI.java b/src/jalview/datamodel/AlignmentI.java index 084b80e..5fb16d6 100755 --- a/src/jalview/datamodel/AlignmentI.java +++ b/src/jalview/datamodel/AlignmentI.java @@ -580,6 +580,32 @@ public interface AlignmentI extends AnnotatedCollectionI */ AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo); + /** + * Set the hidden columns collection on the alignment + * + * @param cols + */ public void setHiddenColumns(HiddenColumns cols); + /** + * Set the first sequence as representative and hide its insertions. Typically + * used when loading JPred files. + */ + public void setupJPredAlignment(); + + /** + * Add gaps into the sequences aligned to profileseq under the given + * AlignmentView + * + * @param profileseq + * sequence in al which sequences are aligned to + * @param input + * alignment view where sequence corresponding to profileseq is first + * entry + * @return new HiddenColumns for new alignment view, with insertions into + * profileseq marked as hidden. + */ + public HiddenColumns propagateInsertions(SequenceI profileseq, + AlignmentView input); + } diff --git a/src/jalview/datamodel/Annotation.java b/src/jalview/datamodel/Annotation.java index ae29417..f6919cd 100755 --- a/src/jalview/datamodel/Annotation.java +++ b/src/jalview/datamodel/Annotation.java @@ -210,7 +210,10 @@ public class Annotation return ((value == 0f) && ((description == null) || (description.trim().length() == 0)) && ((displayCharacter == null) - || (displayCharacter.trim().length() == 0)) + || (displayCharacter.trim().length() == 0) + || (displayCharacter.equals(" ."))) // RNA Stockholm blank + // displayCharacter can + // end up like this && (secondaryStructure == '\0' || (secondaryStructure == ' ')) && colour == null); } diff --git a/src/jalview/datamodel/CigarArray.java b/src/jalview/datamodel/CigarArray.java index 1723f1d..17e9ea6 100644 --- a/src/jalview/datamodel/CigarArray.java +++ b/src/jalview/datamodel/CigarArray.java @@ -20,7 +20,7 @@ */ package jalview.datamodel; -import java.util.List; +import java.util.Iterator; public class CigarArray extends CigarBase { @@ -90,9 +90,7 @@ public class CigarArray extends CigarBase SequenceGroup selectionGroup) { this(constructSeqCigarArray(alignment, selectionGroup)); - constructFromAlignment(alignment, - hidden != null ? hidden.getHiddenColumnsCopy() : null, - selectionGroup); + constructFromAlignment(alignment, hidden, selectionGroup); } private static int[] _calcStartEndBounds(AlignmentI alignment, @@ -154,33 +152,25 @@ public class CigarArray extends CigarBase * @param selectionGroup */ private void constructFromAlignment(AlignmentI alignment, - List list, SequenceGroup selectionGroup) + HiddenColumns hidden, SequenceGroup selectionGroup) { int[] _startend = _calcStartEndBounds(alignment, selectionGroup); - int start = _startend[1], end = _startend[2]; + int start = _startend[1]; + int end = _startend[2]; // now construct the CigarArray operations - if (list != null) + if (hidden != null) { int[] region; - int hideStart, hideEnd; + int hideStart; + int hideEnd; int last = start; - for (int j = 0; last < end & j < list.size(); j++) + + Iterator regions = hidden.getBoundedIterator(start, end); + while (regions.hasNext()) { - region = list.get(j); + region = regions.next(); hideStart = region[0]; hideEnd = region[1]; - // edit hidden regions to selection range - - // just move on if hideEnd is before last - if (hideEnd < last) - { - continue; - } - // exit if next region is after end - if (hideStart > end) - { - break; - } // truncate region at start if last falls in region if ((hideStart < last) && (hideEnd >= last)) @@ -204,6 +194,7 @@ public class CigarArray extends CigarBase addOperation(CigarArray.D, 1 + hideEnd - hideStart); last = hideEnd + 1; } + // Final match if necessary. if (last <= end) { diff --git a/src/jalview/datamodel/DBRefEntry.java b/src/jalview/datamodel/DBRefEntry.java index f7837f7..98868ce 100755 --- a/src/jalview/datamodel/DBRefEntry.java +++ b/src/jalview/datamodel/DBRefEntry.java @@ -27,7 +27,20 @@ import java.util.List; public class DBRefEntry implements DBRefEntryI { - String source = "", version = "", accessionId = ""; + /* + * the mapping to chromosome (genome) is held as an instance with + * source = speciesId + * version = assemblyId + * accessionId = "chromosome:" + chromosomeId + * map = mapping from sequence to reference assembly + */ + public static final String CHROMOSOME = "chromosome"; + + String source = ""; + + String version = ""; + + String accessionId = ""; /** * maps from associated sequence to the database sequence's coordinate system @@ -331,4 +344,14 @@ public class DBRefEntry implements DBRefEntryI } return true; } + + /** + * Mappings to chromosome are held with accessionId as "chromosome:id" + * + * @return + */ + public boolean isChromosome() + { + return accessionId != null && accessionId.startsWith(CHROMOSOME + ":"); + } } diff --git a/src/jalview/datamodel/DBRefSource.java b/src/jalview/datamodel/DBRefSource.java index 0ac14e5..7a30141 100755 --- a/src/jalview/datamodel/DBRefSource.java +++ b/src/jalview/datamodel/DBRefSource.java @@ -96,7 +96,7 @@ public class DBRefSource * List of databases whose sequences might have coding regions annotated */ public static final String[] DNACODINGDBS = { EMBL, EMBLCDS, GENEDB, - ENSEMBL }; + ENSEMBL, ENSEMBLGENOMES }; public static final String[] CODINGDBS = { EMBLCDS, GENEDB, ENSEMBL }; @@ -105,7 +105,7 @@ public class DBRefSource public static String[] allSources() { - List src = new ArrayList(); + List src = new ArrayList<>(); for (Field f : DBRefSource.class.getFields()) { if (String.class.equals(f.getType())) diff --git a/src/jalview/datamodel/GeneLociI.java b/src/jalview/datamodel/GeneLociI.java new file mode 100644 index 0000000..f8c7ec5 --- /dev/null +++ b/src/jalview/datamodel/GeneLociI.java @@ -0,0 +1,38 @@ +package jalview.datamodel; + +import jalview.util.MapList; + +/** + * An interface to model one or more contiguous regions on one chromosome + */ +public interface GeneLociI +{ + /** + * Answers the species identifier + * + * @return + */ + String getSpeciesId(); + + /** + * Answers the reference assembly identifier + * + * @return + */ + String getAssemblyId(); + + /** + * Answers the chromosome identifier e.g. "2", "Y", "II" + * + * @return + */ + String getChromosomeId(); + + /** + * Answers the mapping from sequence to chromosome loci. For a reverse strand + * mapping, the chromosomal ranges will have start > end. + * + * @return + */ + MapList getMap(); +} diff --git a/src/jalview/datamodel/HiddenColumns.java b/src/jalview/datamodel/HiddenColumns.java index c0a43ee..a7e93da 100644 --- a/src/jalview/datamodel/HiddenColumns.java +++ b/src/jalview/datamodel/HiddenColumns.java @@ -20,25 +20,71 @@ */ package jalview.datamodel; -import jalview.util.Comparison; -import jalview.util.ShiftList; - import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; -import java.util.Collections; +import java.util.Iterator; import java.util.List; -import java.util.Vector; import java.util.concurrent.locks.ReentrantReadWriteLock; +/** + * This class manages the collection of hidden columns associated with an + * alignment. To iterate over the collection, or over visible columns/regions, + * use an iterator obtained from one of: + * + * - getBoundedIterator: iterates over the hidden regions, within some bounds, + * returning *absolute* positions + * + * - getBoundedStartIterator: iterates over the start positions of hidden + * regions, within some bounds, returning *visible* positions + * + * - getVisContigsIterator: iterates over visible regions in a range, returning + * *absolute* positions + * + * - getVisibleColsIterator: iterates over the visible *columns* + * + * For performance reasons, provide bounds where possible. Note that column + * numbering begins at 0 throughout this class. + * + * @author kmourao + */ + +/* Implementation notes: + * + * Methods which change the hiddenColumns collection should use a writeLock to + * prevent other threads accessing the hiddenColumns collection while changes + * are being made. They should also reset the hidden columns cursor, and either + * update the hidden columns count, or set it to 0 (so that it will later be + * updated when needed). + * + * + * Methods which only need read access to the hidden columns collection should + * use a readLock to prevent other threads changing the hidden columns + * collection while it is in use. + */ public class HiddenColumns { + private static final int HASH_MULTIPLIER = 31; + private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); /* + * Cursor which tracks the last used hidden columns region, and the number + * of hidden columns up to (but not including) that region. + */ + private HiddenColumnsCursor cursor = new HiddenColumnsCursor(); + + /* + * cache of the number of hidden columns: must be kept up to date by methods + * which add or remove hidden columns + */ + private int numColumns = 0; + + /* * list of hidden column [start, end] ranges; the list is maintained in * ascending start column order */ - private ArrayList hiddenColumns; + private List hiddenColumns = new ArrayList<>(); /** * Constructor @@ -51,19 +97,263 @@ public class HiddenColumns * Copy constructor * * @param copy + * the HiddenColumns object to copy from */ public HiddenColumns(HiddenColumns copy) { + this(copy, Integer.MIN_VALUE, Integer.MAX_VALUE, 0); + } + + /** + * Copy constructor within bounds and with offset. Copies hidden column + * regions fully contained between start and end, and offsets positions by + * subtracting offset. + * + * @param copy + * HiddenColumns instance to copy from + * @param start + * lower bound to copy from + * @param end + * upper bound to copy to + * @param offset + * offset to subtract from each region boundary position + * + */ + public HiddenColumns(HiddenColumns copy, int start, int end, int offset) + { try { LOCK.writeLock().lock(); if (copy != null) { - if (copy.hiddenColumns != null) + numColumns = 0; + Iterator it = copy.getBoundedIterator(start, end); + while (it.hasNext()) + { + int[] region = it.next(); + // still need to check boundaries because iterator returns + // all overlapping regions and we need contained regions + if (region[0] >= start && region[1] <= end) + { + hiddenColumns.add( + new int[] + { region[0] - offset, region[1] - offset }); + numColumns += region[1] - region[0] + 1; + } + } + cursor = new HiddenColumnsCursor(hiddenColumns); + } + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Adds the specified column range to the hidden columns collection + * + * @param start + * start of range to add (absolute position in alignment) + * @param end + * end of range to add (absolute position in alignment) + */ + public void hideColumns(int start, int end) + { + try + { + LOCK.writeLock().lock(); + + int previndex = 0; + int prevHiddenCount = 0; + int regionindex = 0; + if (!hiddenColumns.isEmpty()) + { + // set up cursor reset values + HiddenCursorPosition cursorPos = cursor.findRegionForColumn(start, false); + regionindex = cursorPos.getRegionIndex(); + + if (regionindex > 0) + { + // get previous index and hidden count for updating the cursor later + previndex = regionindex - 1; + int[] prevRegion = hiddenColumns.get(previndex); + prevHiddenCount = cursorPos.getHiddenSoFar() + - (prevRegion[1] - prevRegion[0] + 1); + } + } + + // new range follows everything else; check first to avoid looping over + // whole hiddenColumns collection + if (hiddenColumns.isEmpty() + || start > hiddenColumns.get(hiddenColumns.size() - 1)[1]) + { + hiddenColumns.add(new int[] { start, end }); + numColumns += end - start + 1; + } + else + { + /* + * traverse existing hidden ranges and insert / amend / append as + * appropriate + */ + boolean added = false; + if (regionindex > 0) + { + added = insertRangeAtRegion(regionindex - 1, start, end); + } + if (!added && regionindex < hiddenColumns.size()) + { + insertRangeAtRegion(regionindex, start, end); + } + } + + // reset the cursor to just before our insertion point: this saves + // a lot of reprocessing in large alignments + cursor = new HiddenColumnsCursor(hiddenColumns, previndex, + prevHiddenCount); + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Insert [start, range] at the region at index i in hiddenColumns, if + * feasible + * + * @param i + * index to insert at + * @param start + * start of range to insert + * @param end + * end of range to insert + * @return true if range was successfully inserted + */ + private boolean insertRangeAtRegion(int i, int start, int end) + { + boolean added = false; + + int[] region = hiddenColumns.get(i); + if (end < region[0] - 1) + { + /* + * insert discontiguous preceding range + */ + hiddenColumns.add(i, new int[] { start, end }); + numColumns += end - start + 1; + added = true; + } + else if (end <= region[1]) + { + /* + * new range overlaps existing, or is contiguous preceding it - adjust + * start column + */ + int oldstart = region[0]; + region[0] = Math.min(region[0], start); + numColumns += oldstart - region[0]; // new columns are between old and + // adjusted starts + added = true; + } + else if (start <= region[1] + 1) + { + /* + * new range overlaps existing, or is contiguous following it - adjust + * start and end columns + */ + insertRangeAtOverlap(i, start, end, region); + added = true; + } + return added; + } + + /** + * Insert a range whose start position overlaps an existing region and/or is + * contiguous to the right of the region + * + * @param i + * index to insert at + * @param start + * start of range to insert + * @param end + * end of range to insert + * @param region + * the overlapped/continued region + */ + private void insertRangeAtOverlap(int i, int start, int end, int[] region) + { + int oldstart = region[0]; + int oldend = region[1]; + region[0] = Math.min(region[0], start); + region[1] = Math.max(region[1], end); + + numColumns += oldstart - region[0]; + + /* + * also update or remove any subsequent ranges + * that are overlapped + */ + int endi = i; + while (endi < hiddenColumns.size() - 1) + { + int[] nextRegion = hiddenColumns.get(endi + 1); + if (nextRegion[0] > end + 1) + { + /* + * gap to next hidden range - no more to update + */ + break; + } + numColumns -= nextRegion[1] - nextRegion[0] + 1; + region[1] = Math.max(nextRegion[1], end); + endi++; + } + numColumns += region[1] - oldend; + hiddenColumns.subList(i + 1, endi + 1).clear(); + } + + /** + * hide a list of ranges + * + * @param ranges + */ + public void hideList(List ranges) + { + try + { + LOCK.writeLock().lock(); + for (int[] r : ranges) + { + hideColumns(r[0], r[1]); + } + cursor = new HiddenColumnsCursor(hiddenColumns); + + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Unhides, and adds to the selection list, all hidden columns + */ + public void revealAllHiddenColumns(ColumnSelection sel) + { + try + { + LOCK.writeLock().lock(); + + for (int[] region : hiddenColumns) + { + for (int j = region[0]; j < region[1] + 1; j++) { - hiddenColumns = copy.copyHiddenRegionsToArrayList(); + sel.addElement(j); } } + hiddenColumns.clear(); + cursor = new HiddenColumnsCursor(hiddenColumns); + numColumns = 0; + } finally { LOCK.writeLock().unlock(); @@ -71,16 +361,46 @@ public class HiddenColumns } /** - * This method is used to return all the HiddenColumn regions and is intended - * to remain private. External callers which need a copy of the regions can - * call getHiddenColumnsCopyAsList. + * Reveals, and marks as selected, the hidden column range with the given + * start column * - * @return empty list or List of hidden column intervals + * @param start + * the start column to look for + * @param sel + * the column selection to add the hidden column range to */ - private List getHiddenRegions() + public void revealHiddenColumns(int start, ColumnSelection sel) { - return hiddenColumns == null ? Collections. emptyList() - : hiddenColumns; + try + { + LOCK.writeLock().lock(); + + if (!hiddenColumns.isEmpty()) + { + int regionIndex = cursor.findRegionForColumn(start, false) + .getRegionIndex(); + + if (regionIndex != -1 && regionIndex != hiddenColumns.size()) + { + // regionIndex is the region which either contains start + // or lies to the right of start + int[] region = hiddenColumns.get(regionIndex); + if (start == region[0]) + { + for (int j = region[0]; j < region[1] + 1; j++) + { + sel.addElement(j); + } + int colsToRemove = region[1] - region[0] + 1; + hiddenColumns.remove(regionIndex); + numColumns -= colsToRemove; + } + } + } + } finally + { + LOCK.writeLock().unlock(); + } } /** @@ -99,16 +419,22 @@ public class HiddenColumns { LOCK.readLock().lock(); StringBuilder regionBuilder = new StringBuilder(); - if (hiddenColumns != null) + + boolean first = true; + for (int[] range : hiddenColumns) { - for (int[] range : hiddenColumns) + if (!first) + { + regionBuilder.append(delimiter); + } + else { - regionBuilder.append(delimiter).append(range[0]).append(between) - .append(range[1]); + first = false; } + regionBuilder.append(range[0]).append(between).append(range[1]); - regionBuilder.deleteCharAt(0); } + return regionBuilder.toString(); } finally { @@ -123,18 +449,20 @@ public class HiddenColumns */ public int getSize() { + return numColumns; + } + + /** + * Get the number of distinct hidden regions + * + * @return number of regions + */ + public int getNumberOfRegions() + { try { LOCK.readLock().lock(); - int size = 0; - if (hasHiddenColumns()) - { - for (int[] range : hiddenColumns) - { - size += range[1] - range[0] + 1; - } - } - return size; + return hiddenColumns.size(); } finally { LOCK.readLock().unlock(); @@ -157,25 +485,23 @@ public class HiddenColumns /* * check hidden columns are either both null, or match */ - if (this.hiddenColumns == null) - { - return (that.hiddenColumns == null); - } - if (that.hiddenColumns == null - || that.hiddenColumns.size() != this.hiddenColumns.size()) + + if (that.hiddenColumns.size() != this.hiddenColumns.size()) { return false; } - int i = 0; - for (int[] thisRange : hiddenColumns) + + Iterator it = this.iterator(); + Iterator thatit = that.iterator(); + while (it.hasNext()) { - int[] thatRange = that.hiddenColumns.get(i++); - if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1]) + if (!(Arrays.equals(it.next(), thatit.next()))) { return false; } } return true; + } finally { LOCK.readLock().unlock(); @@ -189,23 +515,19 @@ public class HiddenColumns * int column index in alignment view (count from zero) * @return alignment column index for column */ - public int adjustForHiddenColumns(int column) + public int visibleToAbsoluteColumn(int column) { try { LOCK.readLock().lock(); int result = column; - if (hiddenColumns != null) + + if (!hiddenColumns.isEmpty()) { - for (int i = 0; i < hiddenColumns.size(); i++) - { - int[] region = hiddenColumns.get(i); - if (result >= region[0]) - { - result += region[1] - region[0] + 1; - } - } + result += cursor.findRegionForColumn(column, true) + .getHiddenSoFar(); } + return result; } finally { @@ -216,54 +538,52 @@ public class HiddenColumns /** * Use this method to find out where a column will appear in the visible * alignment when hidden columns exist. If the column is not visible, then the - * left-most visible column will always be returned. + * index of the next visible column on the left will be returned (or 0 if + * there is no visible column on the left) * * @param hiddenColumn * the column index in the full alignment including hidden columns * @return the position of the column in the visible alignment */ - public int findColumnPosition(int hiddenColumn) + public int absoluteToVisibleColumn(int hiddenColumn) { try { LOCK.readLock().lock(); int result = hiddenColumn; - if (hiddenColumns != null) - { - int index = 0; - int[] region; - do - { - region = hiddenColumns.get(index++); - if (hiddenColumn > region[1]) - { - result -= region[1] + 1 - region[0]; - } - } while ((hiddenColumn > region[1]) - && (index < hiddenColumns.size())); - if (hiddenColumn >= region[0] && hiddenColumn <= region[1]) + if (!hiddenColumns.isEmpty()) + { + HiddenCursorPosition cursorPos = cursor + .findRegionForColumn(hiddenColumn, false); + int index = cursorPos.getRegionIndex(); + int hiddenBeforeCol = cursorPos.getHiddenSoFar(); + + // just subtract hidden cols count - this works fine if column is + // visible + result = hiddenColumn - hiddenBeforeCol; + + // now check in case column is hidden - it will be in the returned + // hidden region + if (index < hiddenColumns.size()) { - // Here the hidden column is within a region, so - // we want to return the position of region[0]-1, adjusted for any - // earlier hidden columns. - // Calculate the difference between the actual hidden col position - // and region[0]-1, and then subtract from result to convert result - // from - // the adjusted hiddenColumn value to the adjusted region[0]-1 value - - // However, if the region begins at 0 we cannot return region[0]-1 - // just return 0 - if (region[0] == 0) - { - return 0; - } - else + int[] region = hiddenColumns.get(index); + if (hiddenColumn >= region[0] && hiddenColumn <= region[1]) { - return result - (hiddenColumn - region[0] + 1); + // actually col is hidden, return region[0]-1 + // unless region[0]==0 in which case return 0 + if (region[0] == 0) + { + result = 0; + } + else + { + result = region[0] - 1 - hiddenBeforeCol; + } } } } + return result; // return the shifted position after removing hidden // columns. } finally @@ -274,113 +594,75 @@ public class HiddenColumns /** * Find the visible column which is a given visible number of columns to the - * left of another visible column. i.e. for a startColumn x, the column which - * is distance 1 away will be column x-1. + * left (negative visibleDistance) or right (positive visibleDistance) of + * startColumn. If startColumn is not visible, we use the visible column at + * the left boundary of the hidden region containing startColumn. * * @param visibleDistance - * the number of visible columns to offset by + * the number of visible columns to offset by (left offset = negative + * value; right offset = positive value) * @param startColumn - * the column to start from - * @return the position of the column in the visible alignment + * the position of the column to start from (absolute position) + * @return the position of the column which is away + * (absolute position) */ - public int subtractVisibleColumns(int visibleDistance, int startColumn) + public int offsetByVisibleColumns(int visibleDistance, int startColumn) { try { - LOCK.readLock().lock(); - int distance = visibleDistance; - - // in case startColumn is in a hidden region, move it to the left - int start = adjustForHiddenColumns(findColumnPosition(startColumn)); - - // get index of hidden region to left of start - int index = getHiddenIndexLeft(start); - if (index == -1) - { - // no hidden regions to left of startColumn - return start - distance; - } - - // walk backwards through the alignment subtracting the counts of visible - // columns from distance - int[] region; - int gap = 0; - int nextstart = start; - - while ((index > -1) && (distance - gap > 0)) - { - // subtract the gap to right of region from distance - distance -= gap; - start = nextstart; - - // calculate the next gap - region = hiddenColumns.get(index); - gap = start - region[1]; - - // set start to just to left of current region - nextstart = region[0] - 1; - index--; - } + int start = absoluteToVisibleColumn(startColumn); + return visibleToAbsoluteColumn(start + visibleDistance); - if (distance - gap > 0) - { - // fell out of loop because there are no more hidden regions - distance -= gap; - return nextstart - distance; - } - return start - distance; } finally { LOCK.readLock().unlock(); } - } /** - * Use this method to determine the set of hiddenRegion start positions + * This method returns the rightmost limit of a region of an alignment with + * hidden columns. In otherwords, the next hidden column. * - * @return list of column number in visible view where hidden regions start + * @param alPos + * the absolute (visible) alignmentPosition to find the next hidden + * column for + * @return the index of the next hidden column, or alPos if there is no next + * hidden column */ - public List findHiddenRegionPositions() + public int getNextHiddenBoundary(boolean left, int alPos) { try { LOCK.readLock().lock(); - List positions = null; - - if (hiddenColumns != null) + if (!hiddenColumns.isEmpty()) { - positions = new ArrayList<>(hiddenColumns.size()); + int index = cursor.findRegionForColumn(alPos, false) + .getRegionIndex(); - positions.add(hiddenColumns.get(0)[0]); - for (int i = 1; i < hiddenColumns.size(); ++i) + if (left && index > 0) { - - int result = 0; - if (hiddenColumns != null) + int[] region = hiddenColumns.get(index - 1); + return region[1]; + } + else if (!left && index < hiddenColumns.size()) + { + int[] region = hiddenColumns.get(index); + if (alPos < region[0]) { - int index = 0; - int gaps = 0; - do - { - int[] region = hiddenColumns.get(index); - gaps += region[1] + 1 - region[0]; - result = region[1] + 1; - index++; - } while (index <= i); - - result -= gaps; + return region[0]; + } + else if ((alPos <= region[1]) + && (index + 1 < hiddenColumns.size())) + { + // alPos is within a hidden region, return the next one + // if there is one + region = hiddenColumns.get(index + 1); + return region[0]; } - positions.add(result); } } - else - { - positions = new ArrayList<>(); - } - - return positions; + return alPos; } finally { LOCK.readLock().unlock(); @@ -388,69 +670,54 @@ public class HiddenColumns } /** - * This method returns the rightmost limit of a region of an alignment with - * hidden columns. In otherwords, the next hidden column. + * Answers if a column in the alignment is visible * - * @param index - * int + * @param column + * absolute position of column in the alignment + * @return true if column is visible */ - public int getHiddenBoundaryRight(int alPos) + public boolean isVisible(int column) { try { LOCK.readLock().lock(); - if (hiddenColumns != null) + + if (!hiddenColumns.isEmpty()) { - int index = 0; - do + int regionindex = cursor.findRegionForColumn(column, false) + .getRegionIndex(); + if (regionindex > -1 && regionindex < hiddenColumns.size()) { - int[] region = hiddenColumns.get(index); - if (alPos < region[0]) + int[] region = hiddenColumns.get(regionindex); + // already know that column <= region[1] as cursor returns containing + // region or region to right + if (column >= region[0]) { - return region[0]; + return false; } - - index++; - } while (index < hiddenColumns.size()); + } } + return true; - return alPos; } finally { LOCK.readLock().unlock(); } - } /** - * This method returns the leftmost limit of a region of an alignment with - * hidden columns. In otherwords, the previous hidden column. * - * @param index - * int + * @return true if there are columns hidden */ - public int getHiddenBoundaryLeft(int alPos) + public boolean hasHiddenColumns() { try { LOCK.readLock().lock(); - if (hiddenColumns != null) - { - int index = hiddenColumns.size() - 1; - do - { - int[] region = hiddenColumns.get(index); - if (alPos > region[1]) - { - return region[1]; - } - - index--; - } while (index > -1); - } - - return alPos; + // we don't use getSize()>0 here because it has to iterate over + // the full hiddenColumns collection and so will be much slower + return (!hiddenColumns.isEmpty()); } finally { LOCK.readLock().unlock(); @@ -458,258 +725,169 @@ public class HiddenColumns } /** - * This method returns the index of the hidden region to the left of a column - * position. If the column is in a hidden region it returns the index of the - * region to the left. If there is no hidden region to the left it returns -1. * - * @param pos - * int + * @return true if there is more than one hidden column region */ - private int getHiddenIndexLeft(int pos) + public boolean hasMultiHiddenColumnRegions() { try { - LOCK.readLock().lock(); - if (hiddenColumns != null) - { - int index = hiddenColumns.size() - 1; - do - { - int[] region = hiddenColumns.get(index); - if (pos > region[1]) - { - return index; - } - - index--; - } while (index > -1); - } - - return -1; + return !hiddenColumns.isEmpty() && hiddenColumns.size() > 1; } finally { LOCK.readLock().unlock(); } - } + /** - * Adds the specified column range to the hidden columns - * - * @param start - * @param end + * Returns a hashCode built from hidden column ranges */ - public void hideColumns(int start, int end) + @Override + public int hashCode() { - boolean wasAlreadyLocked = false; try { - // check if the write lock was already locked by this thread, - // as this method can be called internally in loops within HiddenColumns - if (!LOCK.isWriteLockedByCurrentThread()) - { - LOCK.writeLock().lock(); - } - else - { - wasAlreadyLocked = true; - } - - if (hiddenColumns == null) - { - hiddenColumns = new ArrayList<>(); - } + LOCK.readLock().lock(); + int hashCode = 1; - /* - * traverse existing hidden ranges and insert / amend / append as - * appropriate - */ - for (int i = 0; i < hiddenColumns.size(); i++) + for (int[] hidden : hiddenColumns) { - int[] region = hiddenColumns.get(i); - - if (end < region[0] - 1) - { - /* - * insert discontiguous preceding range - */ - hiddenColumns.add(i, new int[] { start, end }); - return; - } - - if (end <= region[1]) - { - /* - * new range overlaps existing, or is contiguous preceding it - adjust - * start column - */ - region[0] = Math.min(region[0], start); - return; - } - - if (start <= region[1] + 1) - { - /* - * new range overlaps existing, or is contiguous following it - adjust - * start and end columns - */ - region[0] = Math.min(region[0], start); - region[1] = Math.max(region[1], end); - - /* - * also update or remove any subsequent ranges - * that are overlapped - */ - while (i < hiddenColumns.size() - 1) - { - int[] nextRegion = hiddenColumns.get(i + 1); - if (nextRegion[0] > end + 1) - { - /* - * gap to next hidden range - no more to update - */ - break; - } - region[1] = Math.max(nextRegion[1], end); - hiddenColumns.remove(i + 1); - } - return; - } + hashCode = HASH_MULTIPLIER * hashCode + hidden[0]; + hashCode = HASH_MULTIPLIER * hashCode + hidden[1]; } - - /* - * remaining case is that the new range follows everything else - */ - hiddenColumns.add(new int[] { start, end }); + return hashCode; } finally { - if (!wasAlreadyLocked) - { - LOCK.writeLock().unlock(); - } + LOCK.readLock().unlock(); } } - public boolean isVisible(int column) + /** + * Hide columns corresponding to the marked bits + * + * @param inserts + * - columns mapped to bits starting from zero + */ + public void hideColumns(BitSet inserts) + { + hideColumns(inserts, 0, inserts.length() - 1); + } + + /** + * Hide columns corresponding to the marked bits, within the range + * [start,end]. Entries in tohide which are outside [start,end] are ignored. + * + * @param tohide + * columns mapped to bits starting from zero + * @param start + * start of range to hide columns within + * @param end + * end of range to hide columns within + */ + private void hideColumns(BitSet tohide, int start, int end) { try { - LOCK.readLock().lock(); - - if (hiddenColumns != null) + LOCK.writeLock().lock(); + for (int firstSet = tohide + .nextSetBit(start), lastSet = start; firstSet >= start + && lastSet <= end; firstSet = tohide + .nextSetBit(lastSet)) { - for (int[] region : hiddenColumns) + lastSet = tohide.nextClearBit(firstSet); + if (lastSet <= end) { - if (column >= region[0] && column <= region[1]) - { - return false; - } + hideColumns(firstSet, lastSet - 1); + } + else if (firstSet <= end) + { + hideColumns(firstSet, end); } } - - return true; + cursor = new HiddenColumnsCursor(hiddenColumns); } finally { - LOCK.readLock().unlock(); - } - } - - private ArrayList copyHiddenRegionsToArrayList() - { - int size = 0; - if (hiddenColumns != null) - { - size = hiddenColumns.size(); - } - ArrayList copy = new ArrayList<>(size); - - for (int i = 0, j = size; i < j; i++) - { - int[] rh; - int[] cp; - rh = hiddenColumns.get(i); - if (rh != null) - { - cp = new int[rh.length]; - System.arraycopy(rh, 0, cp, 0, rh.length); - copy.add(cp); - } + LOCK.writeLock().unlock(); } - - return copy; } /** - * Returns a copy of the vector of hidden regions, as an ArrayList. Before - * using this method please consider if you really need access to the hidden - * regions - a new (or existing!) method on HiddenColumns might be more - * appropriate. + * Hide columns corresponding to the marked bits, within the range + * [start,end]. Entries in tohide which are outside [start,end] are ignored. + * NB Existing entries in [start,end] are cleared. * - * @return hidden regions as an ArrayList of [start,end] pairs + * @param tohide + * columns mapped to bits starting from zero + * @param start + * start of range to hide columns within + * @param end + * end of range to hide columns within */ - public ArrayList getHiddenColumnsCopy() + public void clearAndHideColumns(BitSet tohide, int start, int end) { - try - { - LOCK.readLock().lock(); - return copyHiddenRegionsToArrayList(); - } finally - { - LOCK.readLock().unlock(); - } + clearHiddenColumnsInRange(start, end); + hideColumns(tohide, start, end); } /** - * propagate shift in alignment columns to column selection + * Make all columns in the range [start,end] visible * * @param start - * beginning of edit - * @param left - * shift in edit (+ve for removal, or -ve for inserts) + * start of range to show columns + * @param end + * end of range to show columns */ - public List compensateForEdit(int start, int change, - ColumnSelection sel) + private void clearHiddenColumnsInRange(int start, int end) { try { LOCK.writeLock().lock(); - List deletedHiddenColumns = null; - - if (hiddenColumns != null) + + if (!hiddenColumns.isEmpty()) { - deletedHiddenColumns = new ArrayList<>(); - int hSize = hiddenColumns.size(); - for (int i = 0; i < hSize; i++) + HiddenCursorPosition pos = cursor.findRegionForColumn(start, false); + int index = pos.getRegionIndex(); + + if (index != -1 && index != hiddenColumns.size()) { - int[] region = hiddenColumns.get(i); - if (region[0] > start && start + change > region[1]) + // regionIndex is the region which either contains start + // or lies to the right of start + int[] region = hiddenColumns.get(index); + if (region[0] < start && region[1] >= start) { - deletedHiddenColumns.add(region); - - hiddenColumns.remove(i); - i--; - hSize--; - continue; + // region contains start, truncate so that it ends just before start + numColumns -= region[1] - start + 1; + region[1] = start - 1; + index++; } - if (region[0] > start) + int endi = index; + while (endi < hiddenColumns.size()) { - region[0] -= change; - region[1] -= change; - } + region = hiddenColumns.get(endi); - if (region[0] < 0) - { - region[0] = 0; + if (region[1] > end) + { + if (region[0] <= end) + { + // region contains end, truncate so it starts just after end + numColumns -= end - region[0] + 1; + region[0] = end + 1; + } + break; + } + + numColumns -= region[1] - region[0] + 1; + endi++; } + hiddenColumns.subList(index, endi).clear(); } - this.revealHiddenColumns(0, sel); + cursor = new HiddenColumnsCursor(hiddenColumns); } - - return deletedHiddenColumns; } finally { LOCK.writeLock().unlock(); @@ -717,47 +895,24 @@ public class HiddenColumns } /** - * propagate shift in alignment columns to column selection special version of - * compensateForEdit - allowing for edits within hidden regions * - * @param start - * beginning of edit - * @param left - * shift in edit (+ve for removal, or -ve for inserts) + * @param updates + * BitSet where hidden columns will be marked */ - public void compensateForDelEdits(int start, int change) + protected void andNot(BitSet updates) { try { LOCK.writeLock().lock(); - if (hiddenColumns != null) - { - for (int i = 0; i < hiddenColumns.size(); i++) - { - int[] region = hiddenColumns.get(i); - if (region[0] >= start) - { - region[0] -= change; - } - if (region[1] >= start) - { - region[1] -= change; - } - if (region[1] < region[0]) - { - hiddenColumns.remove(i--); - } - if (region[0] < 0) - { - region[0] = 0; - } - if (region[1] < 0) - { - region[1] = 0; - } - } + BitSet hiddenBitSet = new BitSet(); + for (int[] range : hiddenColumns) + { + hiddenBitSet.set(range[0], range[1] + 1); } + hiddenBitSet.andNot(updates); + hiddenColumns.clear(); + hideColumns(hiddenBitSet); } finally { LOCK.writeLock().unlock(); @@ -765,135 +920,80 @@ public class HiddenColumns } /** - * return all visible segments between the given start and end boundaries + * Calculate the visible start and end index of an alignment. * - * @param start - * (first column inclusive from 0) - * @param end - * (last column - not inclusive) - * @return int[] {i_start, i_end, ..} where intervals lie in - * start<=i_start<=i_end 0) - { - List visiblecontigs = new ArrayList<>(); - List regions = getHiddenRegions(); - int vstart = start; - int[] region; - int hideStart; - int hideEnd; + int firstVisible = 0; + int lastVisible = width - 1; - for (int j = 0; vstart < end && j < regions.size(); j++) - { - region = regions.get(j); - hideStart = region[0]; - hideEnd = region[1]; - - if (hideEnd < vstart) - { - continue; - } - if (hideStart > vstart) - { - visiblecontigs.add(new int[] { vstart, hideStart - 1 }); - } - vstart = hideEnd + 1; - } + if (!hiddenColumns.isEmpty()) + { + // first visible col with index 0, convert to absolute index + firstVisible = visibleToAbsoluteColumn(0); - if (vstart < end) + // last visible column is either immediately to left of + // last hidden region, or is just the last column in the alignment + int[] lastregion = hiddenColumns.get(hiddenColumns.size() - 1); + if (lastregion[1] == width - 1) { - visiblecontigs.add(new int[] { vstart, end - 1 }); + // last region is at very end of alignment + // last visible column immediately precedes it + lastVisible = lastregion[0] - 1; } - int[] vcontigs = new int[visiblecontigs.size() * 2]; - for (int i = 0, j = visiblecontigs.size(); i < j; i++) - { - int[] vc = visiblecontigs.get(i); - visiblecontigs.set(i, null); - vcontigs[i * 2] = vc[0]; - vcontigs[i * 2 + 1] = vc[1]; - } - visiblecontigs.clear(); - return vcontigs; - } - else - { - return new int[] { start, end - 1 }; } + return new int[] { firstVisible, lastVisible }; + } finally { LOCK.readLock().unlock(); } } - public String[] getVisibleSequenceStrings(int start, int end, - SequenceI[] seqs) + /** + * Finds the hidden region (if any) which starts or ends at res + * + * @param res + * visible residue position, unadjusted for hidden columns + * @return region as [start,end] or null if no matching region is found. If + * res is adjacent to two regions, returns the left region. + */ + public int[] getRegionWithEdgeAtRes(int res) { try { LOCK.readLock().lock(); - int iSize = seqs.length; - String[] selections = new String[iSize]; - if (hiddenColumns != null && hiddenColumns.size() > 0) - { - for (int i = 0; i < iSize; i++) - { - StringBuffer visibleSeq = new StringBuffer(); - List regions = getHiddenRegions(); - - int blockStart = start; - int blockEnd = end; - int[] region; - int hideStart; - int hideEnd; + int adjres = visibleToAbsoluteColumn(res); - for (int j = 0; j < regions.size(); j++) - { - region = regions.get(j); - hideStart = region[0]; - hideEnd = region[1]; - - if (hideStart < start) - { - continue; - } - - blockStart = Math.min(blockStart, hideEnd + 1); - blockEnd = Math.min(blockEnd, hideStart); - - if (blockStart > blockEnd) - { - break; - } - - visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd)); - - blockStart = hideEnd + 1; - blockEnd = end; - } - - if (end > blockStart) - { - visibleSeq.append(seqs[i].getSequence(blockStart, end)); - } + int[] reveal = null; - selections[i] = visibleSeq.toString(); - } - } - else + if (!hiddenColumns.isEmpty()) { - for (int i = 0; i < iSize; i++) + // look for a region ending just before adjres + int regionindex = cursor.findRegionForColumn(adjres - 1, false) + .getRegionIndex(); + if (regionindex < hiddenColumns.size() + && hiddenColumns.get(regionindex)[1] == adjres - 1) + { + reveal = hiddenColumns.get(regionindex); + } + // check if the region ends just after adjres + else if (regionindex < hiddenColumns.size() + && hiddenColumns.get(regionindex)[0] == adjres + 1) { - selections[i] = seqs[i].getSequenceAsString(start, end); + reveal = hiddenColumns.get(regionindex); } } + return reveal; - return selections; } finally { LOCK.readLock().unlock(); @@ -901,92 +1001,14 @@ public class HiddenColumns } /** - * Locate the first and last position visible for this sequence. if seq isn't - * visible then return the position of the left and right of the hidden - * boundary region, and the corresponding alignment column indices for the - * extent of the sequence - * - * @param seq - * @return int[] { visible start, visible end, first seqpos, last seqpos, - * alignment index for seq start, alignment index for seq end } + * Return an iterator over the hidden regions */ - public int[] locateVisibleBoundsOfSequence(SequenceI seq) + public Iterator iterator() { try { LOCK.readLock().lock(); - int fpos = seq.getStart(); - int lpos = seq.getEnd(); - int start = 0; - - if (hiddenColumns == null || hiddenColumns.size() == 0) - { - int ifpos = seq.findIndex(fpos) - 1; - int ilpos = seq.findIndex(lpos) - 1; - return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos }; - } - - // Simply walk along the sequence whilst watching for hidden column - // boundaries - List regions = getHiddenRegions(); - int spos = fpos; - int lastvispos = -1; - int rcount = 0; - int hideStart = seq.getLength(); - int hideEnd = -1; - int visPrev = 0; - int visNext = 0; - int firstP = -1; - int lastP = -1; - boolean foundStart = false; - for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd() - && p < pLen; p++) - { - if (!Comparison.isGap(seq.getCharAt(p))) - { - // keep track of first/last column - // containing sequence data regardless of visibility - if (firstP == -1) - { - firstP = p; - } - lastP = p; - // update hidden region start/end - while (hideEnd < p && rcount < regions.size()) - { - int[] region = regions.get(rcount++); - visPrev = visNext; - visNext += region[0] - visPrev; - hideStart = region[0]; - hideEnd = region[1]; - } - if (hideEnd < p) - { - hideStart = seq.getLength(); - } - // update visible boundary for sequence - if (p < hideStart) - { - if (!foundStart) - { - fpos = spos; - start = p; - foundStart = true; - } - lastvispos = p; - lpos = spos; - } - // look for next sequence position - spos++; - } - } - if (foundStart) - { - return new int[] { findColumnPosition(start), - findColumnPosition(lastvispos), fpos, lpos, firstP, lastP }; - } - // otherwise, sequence was completely hidden - return new int[] { visPrev, visNext, 0, 0, firstP, lastP }; + return new RangeIterator(hiddenColumns); } finally { LOCK.readLock().unlock(); @@ -994,120 +1016,54 @@ public class HiddenColumns } /** - * delete any columns in alignmentAnnotation that are hidden (including - * sequence associated annotation). + * Return a bounded iterator over the hidden regions * - * @param alignmentAnnotation + * @param start + * position to start from (inclusive, absolute column position) + * @param end + * position to end at (inclusive, absolute column position) + * @return */ - public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation) + public Iterator getBoundedIterator(int start, int end) { - makeVisibleAnnotation(-1, -1, alignmentAnnotation); + try + { + LOCK.readLock().lock(); + return new RangeIterator(start, end, hiddenColumns); + } finally + { + LOCK.readLock().unlock(); + } } /** - * delete any columns in alignmentAnnotation that are hidden (including - * sequence associated annotation). + * Return a bounded iterator over the *visible* start positions of hidden + * regions * * @param start - * remove any annotation to the right of this column + * position to start from (inclusive, visible column position) * @param end - * remove any annotation to the left of this column - * @param alignmentAnnotation - * the annotation to operate on + * position to end at (inclusive, visible column position) */ - public void makeVisibleAnnotation(int start, int end, - AlignmentAnnotation alignmentAnnotation) + public Iterator getStartRegionIterator(int start, int end) { try { LOCK.readLock().lock(); - if (alignmentAnnotation.annotations == null) - { - return; - } - if (start == end && end == -1) - { - start = 0; - end = alignmentAnnotation.annotations.length; - } - if (hiddenColumns != null && hiddenColumns.size() > 0) - { - // then mangle the alignmentAnnotation annotation array - Vector annels = new Vector<>(); - Annotation[] els = null; - List regions = getHiddenRegions(); - int blockStart = start; - int blockEnd = end; - int[] region; - int hideStart; - int hideEnd; - int w = 0; - - for (int j = 0; j < regions.size(); j++) - { - region = regions.get(j); - hideStart = region[0]; - hideEnd = region[1]; - - if (hideStart < start) - { - continue; - } - - blockStart = Math.min(blockStart, hideEnd + 1); - blockEnd = Math.min(blockEnd, hideStart); - - if (blockStart > blockEnd) - { - break; - } - - annels.addElement(els = new Annotation[blockEnd - blockStart]); - System.arraycopy(alignmentAnnotation.annotations, blockStart, els, - 0, els.length); - w += els.length; - blockStart = hideEnd + 1; - blockEnd = end; - } - if (end > blockStart) - { - annels.addElement(els = new Annotation[end - blockStart + 1]); - if ((els.length - + blockStart) <= alignmentAnnotation.annotations.length) - { - // copy just the visible segment of the annotation row - System.arraycopy(alignmentAnnotation.annotations, blockStart, - els, 0, els.length); - } - else - { - // copy to the end of the annotation row - System.arraycopy(alignmentAnnotation.annotations, blockStart, - els, 0, - (alignmentAnnotation.annotations.length - blockStart)); - } - w += els.length; - } - if (w == 0) - { - return; - } + // get absolute position of column in alignment + int absoluteStart = visibleToAbsoluteColumn(start); - alignmentAnnotation.annotations = new Annotation[w]; - w = 0; + // Get cursor position and supply it to the iterator: + // Since we want visible region start, we look for a cursor for the + // (absoluteStart-1), then if absoluteStart is the start of a visible + // region we'll get the cursor pointing to the region before, which is + // what we want + HiddenCursorPosition pos = cursor + .findRegionForColumn(absoluteStart - 1, false); - for (Annotation[] chnk : annels) - { - System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w, - chnk.length); - w += chnk.length; - } - } - else - { - alignmentAnnotation.restrict(start, end); - } + return new StartRegionIterator(pos, start, end, + hiddenColumns); } finally { LOCK.readLock().unlock(); @@ -1115,15 +1071,21 @@ public class HiddenColumns } /** + * Return an iterator over visible *columns* (not regions) between the given + * start and end boundaries * - * @return true if there are columns hidden + * @param start + * first column (inclusive) + * @param end + * last column (inclusive) */ - public boolean hasHiddenColumns() + public Iterator getVisibleColsIterator(int start, int end) { try { LOCK.readLock().lock(); - return hiddenColumns != null && hiddenColumns.size() > 0; + return new RangeElementsIterator( + new VisibleContigsIterator(start, end + 1, hiddenColumns)); } finally { LOCK.readLock().unlock(); @@ -1131,577 +1093,36 @@ public class HiddenColumns } /** + * return an iterator over visible segments between the given start and end + * boundaries * - * @return true if there are more than one set of columns hidden + * @param start + * first column, inclusive from 0 + * @param end + * last column - not inclusive + * @param useVisibleCoords + * if true, start and end are visible column positions, not absolute + * positions* */ - public boolean hasManyHiddenColumns() + public VisibleContigsIterator getVisContigsIterator(int start, + int end, + boolean useVisibleCoords) { + int adjstart = start; + int adjend = end; + if (useVisibleCoords) + { + adjstart = visibleToAbsoluteColumn(start); + adjend = visibleToAbsoluteColumn(end); + } + try { LOCK.readLock().lock(); - return hiddenColumns != null && hiddenColumns.size() > 1; + return new VisibleContigsIterator(adjstart, adjend, hiddenColumns); } finally { LOCK.readLock().unlock(); } } - - /** - * mark the columns corresponding to gap characters as hidden in the column - * selection - * - * @param sr - */ - public void hideInsertionsFor(SequenceI sr) - { - try - { - LOCK.writeLock().lock(); - List inserts = sr.getInsertions(); - for (int[] r : inserts) - { - hideColumns(r[0], r[1]); - } - } finally - { - LOCK.writeLock().unlock(); - } - } - - /** - * Unhides, and adds to the selection list, all hidden columns - */ - public void revealAllHiddenColumns(ColumnSelection sel) - { - try - { - LOCK.writeLock().lock(); - if (hiddenColumns != null) - { - for (int i = 0; i < hiddenColumns.size(); i++) - { - int[] region = hiddenColumns.get(i); - for (int j = region[0]; j < region[1] + 1; j++) - { - sel.addElement(j); - } - } - } - - hiddenColumns = null; - } finally - { - LOCK.writeLock().unlock(); - } - } - - /** - * Reveals, and marks as selected, the hidden column range with the given - * start column - * - * @param start - */ - public void revealHiddenColumns(int start, ColumnSelection sel) - { - try - { - LOCK.writeLock().lock(); - for (int i = 0; i < hiddenColumns.size(); i++) - { - int[] region = hiddenColumns.get(i); - if (start == region[0]) - { - for (int j = region[0]; j < region[1] + 1; j++) - { - sel.addElement(j); - } - - hiddenColumns.remove(region); - break; - } - } - if (hiddenColumns.size() == 0) - { - hiddenColumns = null; - } - } finally - { - LOCK.writeLock().unlock(); - } - } - - /** - * removes intersection of position,length ranges in deletions from the - * start,end regions marked in intervals. - * - * @param shifts - * @param intervals - * @return - */ - private boolean pruneIntervalList(final List shifts, - ArrayList intervals) - { - boolean pruned = false; - int i = 0; - int j = intervals.size() - 1; - int s = 0; - int t = shifts.size() - 1; - int[] hr = intervals.get(i); - int[] sr = shifts.get(s); - while (i <= j && s <= t) - { - boolean trailinghn = hr[1] >= sr[0]; - if (!trailinghn) - { - if (i < j) - { - hr = intervals.get(++i); - } - else - { - i++; - } - continue; - } - int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert - if (endshift < hr[0] || endshift < sr[0]) - { // leadinghc disjoint or not a deletion - if (s < t) - { - sr = shifts.get(++s); - } - else - { - s++; - } - continue; - } - boolean leadinghn = hr[0] >= sr[0]; - boolean leadinghc = hr[0] < endshift; - boolean trailinghc = hr[1] < endshift; - if (leadinghn) - { - if (trailinghc) - { // deleted hidden region. - intervals.remove(i); - pruned = true; - j--; - if (i <= j) - { - hr = intervals.get(i); - } - continue; - } - if (leadinghc) - { - hr[0] = endshift; // clip c terminal region - leadinghn = !leadinghn; - pruned = true; - } - } - if (!leadinghn) - { - if (trailinghc) - { - if (trailinghn) - { - hr[1] = sr[0] - 1; - pruned = true; - } - } - else - { - // sr contained in hr - if (s < t) - { - sr = shifts.get(++s); - } - else - { - s++; - } - continue; - } - } - } - return pruned; // true if any interval was removed or modified by - // operations. - } - - /** - * remove any hiddenColumns or selected columns and shift remaining based on a - * series of position, range deletions. - * - * @param deletions - */ - public void pruneDeletions(List shifts) - { - try - { - LOCK.writeLock().lock(); - // delete any intervals intersecting. - if (hiddenColumns != null) - { - pruneIntervalList(shifts, hiddenColumns); - if (hiddenColumns != null && hiddenColumns.size() == 0) - { - hiddenColumns = null; - } - } - } finally - { - LOCK.writeLock().unlock(); - } - } - - /** - * Add gaps into the sequences aligned to profileseq under the given - * AlignmentView - * - * @param profileseq - * @param al - * - alignment to have gaps inserted into it - * @param input - * - alignment view where sequence corresponding to profileseq is - * first entry - * @return new HiddenColumns for new alignment view, with insertions into - * profileseq marked as hidden. - */ - public static HiddenColumns propagateInsertions(SequenceI profileseq, - AlignmentI al, AlignmentView input) - { - int profsqpos = 0; - - char gc = al.getGapCharacter(); - Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc); - HiddenColumns nview = (HiddenColumns) alandhidden[1]; - SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos]; - nview.propagateInsertions(profileseq, al, origseq); - return nview; - } - - /** - * - * @param profileseq - * - sequence in al which corresponds to origseq - * @param al - * - alignment which is to have gaps inserted into it - * @param origseq - * - sequence corresponding to profileseq which defines gap map for - * modifying al - */ - private void propagateInsertions(SequenceI profileseq, AlignmentI al, - SequenceI origseq) - { - char gc = al.getGapCharacter(); - // recover mapping between sequence's non-gap positions and positions - // mapping to view. - pruneDeletions(ShiftList.parseMap(origseq.gapMap())); - int[] viscontigs = getVisibleContigs(0, profileseq.getLength()); - int spos = 0; - int offset = 0; - - // add profile to visible contigs - for (int v = 0; v < viscontigs.length; v += 2) - { - if (viscontigs[v] > spos) - { - StringBuffer sb = new StringBuffer(); - for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++) - { - sb.append(gc); - } - for (int s = 0, ns = al.getHeight(); s < ns; s++) - { - SequenceI sqobj = al.getSequenceAt(s); - if (sqobj != profileseq) - { - String sq = al.getSequenceAt(s).getSequenceAsString(); - if (sq.length() <= spos + offset) - { - // pad sequence - int diff = spos + offset - sq.length() - 1; - if (diff > 0) - { - // pad gaps - sq = sq + sb; - while ((diff = spos + offset - sq.length() - 1) > 0) - { - // sq = sq - // + ((diff >= sb.length()) ? sb.toString() : sb - // .substring(0, diff)); - if (diff >= sb.length()) - { - sq += sb.toString(); - } - else - { - char[] buf = new char[diff]; - sb.getChars(0, diff, buf, 0); - sq += buf.toString(); - } - } - } - sq += sb.toString(); - } - else - { - al.getSequenceAt(s).setSequence(sq.substring(0, spos + offset) - + sb.toString() + sq.substring(spos + offset)); - } - } - } - // offset+=sb.length(); - } - spos = viscontigs[v + 1] + 1; - } - if ((offset + spos) < profileseq.getLength()) - { - // pad the final region with gaps. - StringBuffer sb = new StringBuffer(); - for (int s = 0, ns = profileseq.getLength() - spos - - offset; s < ns; s++) - { - sb.append(gc); - } - for (int s = 0, ns = al.getHeight(); s < ns; s++) - { - SequenceI sqobj = al.getSequenceAt(s); - if (sqobj == profileseq) - { - continue; - } - String sq = sqobj.getSequenceAsString(); - // pad sequence - int diff = origseq.getLength() - sq.length(); - while (diff > 0) - { - // sq = sq - // + ((diff >= sb.length()) ? sb.toString() : sb - // .substring(0, diff)); - if (diff >= sb.length()) - { - sq += sb.toString(); - } - else - { - char[] buf = new char[diff]; - sb.getChars(0, diff, buf, 0); - sq += buf.toString(); - } - diff = origseq.getLength() - sq.length(); - } - } - } - } - - /** - * remove any hiddenColumns or selected columns and shift remaining based on a - * series of position, range deletions. - * - * @param deletions - */ - private void pruneDeletions(ShiftList deletions) - { - if (deletions != null) - { - final List shifts = deletions.getShifts(); - if (shifts != null && shifts.size() > 0) - { - pruneDeletions(shifts); - - // and shift the rest. - this.compensateForEdits(deletions); - } - } - } - - /** - * Adjust hidden column boundaries based on a series of column additions or - * deletions in visible regions. - * - * @param shiftrecord - * @return - */ - private ShiftList compensateForEdits(ShiftList shiftrecord) - { - if (shiftrecord != null) - { - final List shifts = shiftrecord.getShifts(); - if (shifts != null && shifts.size() > 0) - { - int shifted = 0; - for (int i = 0, j = shifts.size(); i < j; i++) - { - int[] sh = shifts.get(i); - compensateForDelEdits(shifted + sh[0], sh[1]); - shifted -= sh[1]; - } - } - return shiftrecord.getInverse(); - } - return null; - } - - /** - * Returns a hashCode built from hidden column ranges - */ - @Override - public int hashCode() - { - try - { - LOCK.readLock().lock(); - int hashCode = 1; - if (hiddenColumns != null) - { - for (int[] hidden : hiddenColumns) - { - hashCode = 31 * hashCode + hidden[0]; - hashCode = 31 * hashCode + hidden[1]; - } - } - return hashCode; - } finally - { - LOCK.readLock().unlock(); - } - } - - /** - * Hide columns corresponding to the marked bits - * - * @param inserts - * - columns map to bits starting from zero - */ - public void hideMarkedBits(BitSet inserts) - { - try - { - LOCK.writeLock().lock(); - for (int firstSet = inserts - .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts - .nextSetBit(lastSet)) - { - lastSet = inserts.nextClearBit(firstSet); - hideColumns(firstSet, lastSet - 1); - } - } finally - { - LOCK.writeLock().unlock(); - } - } - - /** - * - * @param inserts - * BitSet where hidden columns will be marked - */ - public void markHiddenRegions(BitSet inserts) - { - try - { - LOCK.readLock().lock(); - if (hiddenColumns == null) - { - return; - } - for (int[] range : hiddenColumns) - { - inserts.set(range[0], range[1] + 1); - } - } finally - { - LOCK.readLock().unlock(); - } - } - - /** - * Calculate the visible start and end index of an alignment. - * - * @param width - * full alignment width - * @return integer array where: int[0] = startIndex, and int[1] = endIndex - */ - public int[] getVisibleStartAndEndIndex(int width) - { - try - { - LOCK.readLock().lock(); - int[] alignmentStartEnd = new int[] { 0, width - 1 }; - int startPos = alignmentStartEnd[0]; - int endPos = alignmentStartEnd[1]; - - int[] lowestRange = new int[] { -1, -1 }; - int[] higestRange = new int[] { -1, -1 }; - - if (hiddenColumns == null) - { - return new int[] { startPos, endPos }; - } - - for (int[] hiddenCol : hiddenColumns) - { - lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange; - higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange; - } - - if (lowestRange[0] == -1 && lowestRange[1] == -1) - { - startPos = alignmentStartEnd[0]; - } - else - { - startPos = lowestRange[1] + 1; - } - - if (higestRange[0] == -1 && higestRange[1] == -1) - { - endPos = alignmentStartEnd[1]; - } - else - { - endPos = higestRange[0] - 1; - } - return new int[] { startPos, endPos }; - } finally - { - LOCK.readLock().unlock(); - } - - } - - /** - * Finds the hidden region (if any) which starts or ends at res - * - * @param res - * visible residue position, unadjusted for hidden columns - * @return region as [start,end] or null if no matching region is found - */ - public int[] getRegionWithEdgeAtRes(int res) - { - try - { - LOCK.readLock().lock(); - int adjres = adjustForHiddenColumns(res); - - int[] reveal = null; - if (hiddenColumns != null) - { - for (int[] region : hiddenColumns) - { - if (adjres + 1 == region[0] || adjres - 1 == region[1]) - { - reveal = region; - break; - } - } - } - return reveal; - } finally - { - LOCK.readLock().unlock(); - } - } - } diff --git a/src/jalview/datamodel/HiddenColumnsCursor.java b/src/jalview/datamodel/HiddenColumnsCursor.java new file mode 100644 index 0000000..2e9d798 --- /dev/null +++ b/src/jalview/datamodel/HiddenColumnsCursor.java @@ -0,0 +1,213 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import java.util.ArrayList; +import java.util.List; + +public class HiddenColumnsCursor +{ + // absolute position of first hidden column + private int firstColumn; + + private List hiddenColumns = new ArrayList<>(); + + private HiddenCursorPosition cursorPos = new HiddenCursorPosition(0, 0); + + protected HiddenColumnsCursor() + { + + } + + protected HiddenColumnsCursor(List hiddenCols) + { + resetCursor(hiddenCols, 0, 0); + } + + protected HiddenColumnsCursor(List hiddenCols, int index, + int hiddencount) + { + resetCursor(hiddenCols, index, hiddencount); + } + + /** + * Reset the cursor with a new hidden columns collection, where we know in + * advance the index and hidden columns count of a particular location. + * + * @param hiddenCols + * new hidden columns collection + * @param index + * cursor index to reset to + * @param hiddencount + * hidden columns count to reset to + */ + private void resetCursor(List hiddenCols, int index, + int hiddencount) + { + hiddenColumns = hiddenCols; + if (!hiddenCols.isEmpty()) + { + firstColumn = hiddenColumns.get(0)[0]; + cursorPos = new HiddenCursorPosition(index, + hiddencount); + } + } + + /** + * Get the cursor pointing to the hidden region that column is within (if + * column is hidden) or which is to the right of column (if column is + * visible). If no hidden columns are to the right, returns a cursor pointing + * to an imaginary hidden region beyond the end of the hidden columns + * collection (this ensures the count of previous hidden columns is correct). + * If hidden columns is empty returns null. + * + * @param column + * index of column in visible or absolute coordinates + * @param useVisible + * true if column is in visible coordinates, false if absolute + * @return cursor pointing to hidden region containing the column (if hidden) + * or to the right of the column (if visible) + */ + protected HiddenCursorPosition findRegionForColumn(int column, + boolean useVisible) + { + if (hiddenColumns.isEmpty()) + { + return null; + } + + // used to add in hiddenColumns offset when working with visible columns + int offset = (useVisible ? 1 : 0); + + HiddenCursorPosition pos = cursorPos; + int index = pos.getRegionIndex(); + int hiddenCount = pos.getHiddenSoFar(); + + if (column < firstColumn) + { + pos = new HiddenCursorPosition(0, 0); + } + + // column is after current region + else if ((index < hiddenColumns.size()) + && (hiddenColumns.get(index)[0] <= column + + offset * hiddenCount)) + { + // iterate from where we are now, if we're lucky we'll be close by + // (but still better than iterating from 0) + // stop when we find the region *before* column + // i.e. the next region starts after column or if not, ends after column + pos = searchForward(pos, column, useVisible); + } + + // column is before current region + else + { + pos = searchBackward(pos, column, useVisible); + } + cursorPos = pos; + return pos; + } + + /** + * Search forwards through the hidden columns collection to find the hidden + * region immediately before a column + * + * @param pos + * current position + * @param column + * column to locate + * @param useVisible + * whether using visible or absolute coordinates + * @return position of region before column + */ + private HiddenCursorPosition searchForward(HiddenCursorPosition pos, + int column, boolean useVisible) + { + HiddenCursorPosition p = pos; + if (useVisible) + { + while ((p.getRegionIndex() < hiddenColumns.size()) + && hiddenColumns.get(p.getRegionIndex())[0] <= column + + p.getHiddenSoFar()) + { + p = stepForward(p); + } + } + else + { + while ((p.getRegionIndex() < hiddenColumns.size()) + && hiddenColumns.get(p.getRegionIndex())[1] < column) + { + p = stepForward(p); + } + } + return p; + } + + /** + * Move to the next (rightwards) hidden region after a given cursor position + * + * @param p + * current position of cursor + * @return new position of cursor at next region + */ + private HiddenCursorPosition stepForward(HiddenCursorPosition p) + { + int[] region = hiddenColumns.get(p.getRegionIndex()); + + // increment the index, and add this region's hidden columns to the hidden + // column count + return new HiddenCursorPosition(p.getRegionIndex() + 1, + p.getHiddenSoFar() + region[1] - region[0] + 1); + } + + /** + * Search backwards through the hidden columns collection to find the hidden + * region immediately before (left of) a given column + * + * @param pos + * current position + * @param column + * column to locate + * @param useVisible + * whether using visible or absolute coordinates + * @return position of region immediately to left of column + */ + private HiddenCursorPosition searchBackward(HiddenCursorPosition p, + int column, boolean useVisible) + { + int i = p.getRegionIndex(); + int h = p.getHiddenSoFar(); + + // used to add in hiddenColumns offset when working with visible columns + int offset = (useVisible ? 1 : 0); + + while ((i > 0) && (hiddenColumns.get(i - 1)[1] >= column + offset * h)) + { + i--; + int[] region = hiddenColumns.get(i); + h -= region[1] - region[0] + 1; + } + return new HiddenCursorPosition(i, h); + } + +} diff --git a/src/jalview/datamodel/HiddenCursorPosition.java b/src/jalview/datamodel/HiddenCursorPosition.java new file mode 100644 index 0000000..bdca6d0 --- /dev/null +++ b/src/jalview/datamodel/HiddenCursorPosition.java @@ -0,0 +1,46 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +public final class HiddenCursorPosition +{ + // index of last visited region + private final int regionIndex; + + // number of hidden columns before last visited region + private final int hiddenSoFar; + + public HiddenCursorPosition(int index, int hiddencount) + { + regionIndex = index; + hiddenSoFar = hiddencount; + } + + public int getRegionIndex() + { + return regionIndex; + } + + public int getHiddenSoFar() + { + return hiddenSoFar; + } +} diff --git a/src/jalview/datamodel/RangeElementsIterator.java b/src/jalview/datamodel/RangeElementsIterator.java new file mode 100644 index 0000000..9ca6b2a --- /dev/null +++ b/src/jalview/datamodel/RangeElementsIterator.java @@ -0,0 +1,108 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Iterator over each element in a set of ranges i.e. if ranges is {[3,6], + * [12,15]} it will iterate over {3,4,5,6,12,13,14,15}. Uses a local copy of the + * set of ranges. + * + * @author kmourao + * + */ +public class RangeElementsIterator implements Iterator +{ + private int last; + + private int current; + + private int next; + + private Iterator rangeIterator; + + private int[] nextRange = null; + + RangeElementsIterator(Iterator it) + { + rangeIterator = it; + if (rangeIterator.hasNext()) + { + nextRange = rangeIterator.next(); + next = nextRange[0]; + last = nextRange[1]; + } + } + + @Override + public boolean hasNext() + { + return rangeIterator.hasNext() || next <= last; + } + + @Override + public Integer next() + { + if (!hasNext()) + { + throw new NoSuchElementException(); + } + + current = next; + + // recalculate next + next++; + + // if there are more ranges need to check if next is in a range + checkNextRange(); + return current; + } + + /** + * Check how next position relates to next range, and update next position if + * necessary + */ + private void checkNextRange() + { + if (nextRange != null && next > nextRange[1]) + { + if (rangeIterator.hasNext()) + { + nextRange = rangeIterator.next(); + next = nextRange[0]; + last = nextRange[1]; + } + else + { + nextRange = null; + } + + } + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } +} diff --git a/src/jalview/datamodel/RangeIterator.java b/src/jalview/datamodel/RangeIterator.java new file mode 100644 index 0000000..5d45236 --- /dev/null +++ b/src/jalview/datamodel/RangeIterator.java @@ -0,0 +1,134 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * An iterator which iterates over a list of ranges. Works with a copy of the + * collection of ranges. + */ +public class RangeIterator implements Iterator +{ + // current index in rangeList + private int currentPosition = 0; + + // current range in rangeList + private int[] currentRange; + + // local copy or reference to rangeList + private List localRanges; + + /** + * Unbounded constructor + * + * @param rangeList + * list of ranges to iterate over + */ + RangeIterator(List rangeList) + { + if (!rangeList.isEmpty()) + { + int last = rangeList.get(rangeList.size() - 1)[1]; + init(0, last, rangeList); + } + else + { + init(0, 0, rangeList); + } + } + + /** + * Construct an iterator over rangeList bounded at [lowerBound,upperBound] + * + * @param lowerBound + * lower bound to iterate from + * @param upperBound + * upper bound to iterate to + * @param rangeList + * list of ranges to iterate over + */ + RangeIterator(int lowerBound, int upperBound, + List rangeList) + { + init(lowerBound, upperBound, rangeList); + } + + /** + * Construct an iterator over rangeList bounded at [lowerBound,upperBound] + * + * @param lowerBound + * lower bound to iterate from + * @param upperBound + * upper bound to iterate to + */ + private void init(int lowerBound, int upperBound, + List rangeList) + { + int start = lowerBound; + int end = upperBound; + + if (rangeList != null) + { + localRanges = new ArrayList<>(); + + // iterate until a range overlaps with [start,end] + int i = 0; + while ((i < rangeList.size()) && (rangeList.get(i)[1] < start)) + { + i++; + } + + // iterate from start to end, adding each range. Positions are + // absolute, and all ranges which *overlap* [start,end] are added. + while (i < rangeList.size() && (rangeList.get(i)[0] <= end)) + { + int[] rh = rangeList.get(i); + int[] cp = new int[2]; + System.arraycopy(rh, 0, cp, 0, rh.length); + localRanges.add(cp); + i++; + } + } + } + + @Override + public boolean hasNext() + { + return (localRanges != null) && (currentPosition < localRanges.size()); + } + + @Override + public int[] next() + { + currentRange = localRanges.get(currentPosition); + currentPosition++; + return currentRange; + } + + @Override + public void remove() + { + localRanges.remove(--currentPosition); + } +} diff --git a/src/jalview/datamodel/SearchResults.java b/src/jalview/datamodel/SearchResults.java index cde50e5..d1e3fcc 100755 --- a/src/jalview/datamodel/SearchResults.java +++ b/src/jalview/datamodel/SearchResults.java @@ -267,9 +267,12 @@ public class SearchResults implements SearchResultsI { 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) diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index eaeed9c..f555855 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.Enumeration; +import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Vector; @@ -405,7 +406,7 @@ public class Sequence extends ASequence implements SequenceI { if (pdbIds == null) { - pdbIds = new Vector(); + pdbIds = new Vector<>(); pdbIds.add(entry); return true; } @@ -441,7 +442,7 @@ public class Sequence extends ASequence implements SequenceI @Override public Vector getAllPDBEntries() { - return pdbIds == null ? new Vector() : pdbIds; + return pdbIds == null ? new Vector<>() : pdbIds; } /** @@ -654,10 +655,10 @@ public class Sequence extends ASequence implements SequenceI } /** - * DOCUMENT ME! + * Sets the sequence description, and also parses out any special formats of + * interest * * @param desc - * DOCUMENT ME! */ @Override public void setDescription(String desc) @@ -665,10 +666,67 @@ public class Sequence extends ASequence implements SequenceI this.description = desc; } + @Override + public void setGeneLoci(String speciesId, String assemblyId, + String chromosomeId, MapList map) + { + addDBRef(new DBRefEntry(speciesId, assemblyId, DBRefEntry.CHROMOSOME + + ":" + chromosomeId, new Mapping(map))); + } + /** - * DOCUMENT ME! + * Returns the gene loci mapping for the sequence (may be null) * - * @return DOCUMENT ME! + * @return + */ + @Override + public GeneLociI getGeneLoci() + { + DBRefEntry[] refs = getDBRefs(); + if (refs != null) + { + for (final DBRefEntry ref : refs) + { + if (ref.isChromosome()) + { + return new GeneLociI() + { + @Override + public String getSpeciesId() + { + return ref.getSource(); + } + + @Override + public String getAssemblyId() + { + return ref.getVersion(); + } + + @Override + public String getChromosomeId() + { + // strip off "chromosome:" prefix to chrId + return ref.getAccessionId().substring( + DBRefEntry.CHROMOSOME.length() + 1); + } + + @Override + public MapList getMap() + { + return ref.getMap().getMap(); + } + }; + } + } + } + return null; + } + + /** + * Answers the description + * + * @return */ @Override public String getDescription() @@ -755,7 +813,7 @@ public class Sequence extends ASequence implements SequenceI * @param curs * @return */ - protected int findIndex(int pos, SequenceCursor curs) + protected int findIndex(final int pos, SequenceCursor curs) { if (!isValidCursor(curs)) { @@ -780,10 +838,15 @@ public class Sequence extends ASequence implements SequenceI while (newPos != pos) { col += delta; // shift one column left or right - if (col < 0 || col == sequence.length) + if (col < 0) { break; } + if (col == sequence.length) + { + col--; // return last column if we failed to reach pos + break; + } if (!Comparison.isGap(sequence[col])) { newPos += delta; @@ -791,7 +854,14 @@ public class Sequence extends ASequence implements SequenceI } col++; // convert back to base 1 - updateCursor(pos, col, curs.firstColumnPosition); + + /* + * only update cursor if we found the target position + */ + if (newPos == pos) + { + updateCursor(pos, col, curs.firstColumnPosition); + } return col; } @@ -1068,6 +1138,27 @@ public class Sequence extends ASequence implements SequenceI return map; } + /** + * Build a bitset corresponding to sequence gaps + * + * @return a BitSet where set values correspond to gaps in the sequence + */ + @Override + public BitSet gapBitset() + { + BitSet gaps = new BitSet(sequence.length); + int j = 0; + while (j < sequence.length) + { + if (jalview.util.Comparison.isGap(sequence[j])) + { + gaps.set(j); + } + j++; + } + return gaps; + } + @Override public int[] findPositionMap() { @@ -1091,7 +1182,7 @@ public class Sequence extends ASequence implements SequenceI @Override public List getInsertions() { - ArrayList map = new ArrayList(); + ArrayList map = new ArrayList<>(); int lastj = -1, j = 0; int pos = start; int seqlen = sequence.length; @@ -1403,7 +1494,7 @@ public class Sequence extends ASequence implements SequenceI { if (this.annotation == null) { - this.annotation = new Vector(); + this.annotation = new Vector<>(); } if (!this.annotation.contains(annotation)) { @@ -1570,7 +1661,7 @@ public class Sequence extends ASequence implements SequenceI return null; } - Vector subset = new Vector(); + Vector subset = new Vector<>(); Enumeration e = annotation.elements(); while (e.hasMoreElements()) { @@ -1704,7 +1795,7 @@ public class Sequence extends ASequence implements SequenceI public List getAlignmentAnnotations(String calcId, String label) { - List result = new ArrayList(); + List result = new ArrayList<>(); if (this.annotation != null) { for (AlignmentAnnotation ann : annotation) @@ -1760,7 +1851,7 @@ public class Sequence extends ASequence implements SequenceI } synchronized (dbrefs) { - List primaries = new ArrayList(); + List primaries = new ArrayList<>(); DBRefEntry[] tmp = new DBRefEntry[1]; for (DBRefEntry ref : dbrefs) { @@ -1911,4 +2002,73 @@ public class Sequence extends ASequence implements SequenceI return count; } + + @Override + public String getSequenceStringFromIterator(Iterator it) + { + StringBuilder newSequence = new StringBuilder(); + while (it.hasNext()) + { + int[] block = it.next(); + if (it.hasNext()) + { + newSequence.append(getSequence(block[0], block[1] + 1)); + } + else + { + newSequence.append(getSequence(block[0], block[1])); + } + } + + return newSequence.toString(); + } + + @Override + public int firstResidueOutsideIterator(Iterator regions) + { + int start = 0; + + if (!regions.hasNext()) + { + return findIndex(getStart()) - 1; + } + + // Simply walk along the sequence whilst watching for region + // boundaries + int hideStart = getLength(); + int hideEnd = -1; + boolean foundStart = false; + + // step through the non-gapped positions of the sequence + for (int i = getStart(); i <= getEnd() && (!foundStart); i++) + { + // get alignment position of this residue in the sequence + int p = findIndex(i) - 1; + + // update region start/end + while (hideEnd < p && regions.hasNext()) + { + int[] region = regions.next(); + hideStart = region[0]; + hideEnd = region[1]; + } + if (hideEnd < p) + { + hideStart = getLength(); + } + // update boundary for sequence + if (p < hideStart) + { + start = p; + foundStart = true; + } + } + + if (foundStart) + { + return start; + } + // otherwise, sequence was completely hidden + return 0; + } } diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java index 9c4087e..7052f34 100755 --- a/src/jalview/datamodel/SequenceFeature.java +++ b/src/jalview/datamodel/SequenceFeature.java @@ -20,18 +20,25 @@ */ package jalview.datamodel; +import jalview.datamodel.features.FeatureAttributeType; +import jalview.datamodel.features.FeatureAttributes; import jalview.datamodel.features.FeatureLocationI; +import jalview.datamodel.features.FeatureSourceI; +import jalview.datamodel.features.FeatureSources; +import jalview.util.StringUtils; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.Vector; /** - * DOCUMENT ME! - * - * @author $author$ - * @version $Revision$ + * A class that models a single contiguous feature on a sequence. If flag + * 'contactFeature' is true, the start and end positions are interpreted instead + * as two contact points. */ public class SequenceFeature implements FeatureLocationI { @@ -51,6 +58,8 @@ public class SequenceFeature implements FeatureLocationI // private key for ENA location designed not to conflict with real GFF data private static final String LOCATION = "!Location"; + private static final String ROW_DATA = "%s%s%s"; + /* * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as * name1=value1;name2=value2,value3;...etc @@ -84,6 +93,12 @@ public class SequenceFeature implements FeatureLocationI public Vector links; + /* + * the identifier (if known) for the FeatureSource held in FeatureSources, + * as a provider of metadata about feature attributes + */ + private String source; + /** * Constructs a duplicate feature. Note: Uses makes a shallow copy of the * otherDetails map, so the new and original SequenceFeature may reference the @@ -155,9 +170,11 @@ public class SequenceFeature implements FeatureLocationI this(newType, sf.getDescription(), newBegin, newEnd, newScore, newGroup); + this.source = sf.source; + if (sf.otherDetails != null) { - otherDetails = new HashMap(); + otherDetails = new HashMap<>(); for (Entry entry : sf.otherDetails.entrySet()) { otherDetails.put(entry.getKey(), entry.getValue()); @@ -165,7 +182,7 @@ public class SequenceFeature implements FeatureLocationI } if (sf.links != null && sf.links.size() > 0) { - links = new Vector(); + links = new Vector<>(); for (int i = 0, iSize = sf.links.size(); i < iSize; i++) { links.addElement(sf.links.elementAt(i)); @@ -332,7 +349,7 @@ public class SequenceFeature implements FeatureLocationI { if (links == null) { - links = new Vector(); + links = new Vector<>(); } if (!links.contains(labelLink)) @@ -366,6 +383,30 @@ public class SequenceFeature implements FeatureLocationI } /** + * Answers the value of the specified attribute as string, or null if no such + * value. If more than one attribute name is provided, tries to resolve as keys + * to nested maps. For example, if attribute "CSQ" holds a map of key-value + * pairs, then getValueAsString("CSQ", "Allele") returns the value of "Allele" + * in that map. + * + * @param key + * @return + */ + public String getValueAsString(String... key) + { + if (otherDetails == null) + { + return null; + } + Object value = otherDetails.get(key[0]); + if (key.length > 1 && value instanceof Map) + { + value = ((Map) value).get(key[1]); + } + return value == null ? null : value.toString(); + } + + /** * Returns a property value for the given key if known, else the specified * default value * @@ -394,11 +435,33 @@ public class SequenceFeature implements FeatureLocationI { if (otherDetails == null) { - otherDetails = new HashMap(); + otherDetails = new HashMap<>(); } otherDetails.put(key, value); + recordAttribute(key, value); + } + } + + /** + * Notifies the addition of a feature attribute. This lets us keep track of + * which attributes are present on each feature type, and also the range of + * numerical-valued attributes. + * + * @param key + * @param value + */ + protected void recordAttribute(String key, Object value) + { + String attDesc = null; + if (source != null) + { + attDesc = FeatureSources.getInstance().getSource(source) + .getAttributeName(key); } + + FeatureAttributes.getInstance().addAttribute(this.type, attDesc, value, + key); } /* @@ -535,4 +598,160 @@ public class SequenceFeature implements FeatureLocationI { return begin == 0 && end == 0; } + + /** + * Answers an html-formatted report of feature details + * + * @return + */ + public String getDetailsReport() + { + FeatureSourceI metadata = FeatureSources.getInstance() + .getSource(source); + + StringBuilder sb = new StringBuilder(128); + sb.append("
          "); + sb.append(""); + sb.append(String.format(ROW_DATA, "Type", type, "")); + sb.append(String.format(ROW_DATA, "Start/end", begin == end ? begin + : begin + (isContactFeature() ? ":" : "-") + end, "")); + String desc = StringUtils.stripHtmlTags(description); + sb.append(String.format(ROW_DATA, "Description", desc, "")); + if (!Float.isNaN(score) && score != 0f) + { + sb.append(String.format(ROW_DATA, "Score", score, "")); + } + if (featureGroup != null) + { + sb.append(String.format(ROW_DATA, "Group", featureGroup, "")); + } + + if (otherDetails != null) + { + TreeMap ordered = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); + ordered.putAll(otherDetails); + + for (Entry entry : ordered.entrySet()) + { + String key = entry.getKey(); + if (ATTRIBUTES.equals(key)) + { + continue; // to avoid double reporting + } + + Object value = entry.getValue(); + if (value instanceof Map) + { + /* + * expand values in a Map attribute across separate lines + * copy to a TreeMap for alphabetical ordering + */ + Map values = (Map) value; + SortedMap sm = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); + sm.putAll(values); + for (Entry e : sm.entrySet()) + { + sb.append(String.format(ROW_DATA, key, e.getKey().toString(), e + .getValue().toString())); + } + } + else + { + // tried
          but it failed to provide a tooltip :-( + String attDesc = null; + if (metadata != null) + { + attDesc = metadata.getAttributeName(key); + } + String s = entry.getValue().toString(); + if (isValueInteresting(key, s, metadata)) + { + sb.append(String.format(ROW_DATA, key, attDesc == null ? "" + : attDesc, s)); + } + } + } + } + sb.append("
          "); + + String text = sb.toString(); + return text; + } + + /** + * Answers true if we judge the value is worth displaying, by some heuristic + * rules, else false + * + * @param key + * @param value + * @param metadata + * @return + */ + boolean isValueInteresting(String key, String value, + FeatureSourceI metadata) + { + /* + * currently suppressing zero values as well as null or empty + */ + if (value == null || "".equals(value) || ".".equals(value) + || "0".equals(value)) + { + return false; + } + + if (metadata == null) + { + return true; + } + + FeatureAttributeType attType = metadata.getAttributeType(key); + if (attType != null + && (attType == FeatureAttributeType.Float || attType + .equals(FeatureAttributeType.Integer))) + { + try + { + float fval = Float.valueOf(value); + if (fval == 0f) + { + return false; + } + } catch (NumberFormatException e) + { + // ignore + } + } + + return true; // default to interesting + } + + /** + * Sets the feature source identifier + * + * @param theSource + */ + public void setSource(String theSource) + { + source = theSource; + } +} + +class SFSortByEnd implements Comparator +{ + @Override + public int compare(SequenceFeature a, SequenceFeature b) + { + return a.getEnd() - b.getEnd(); + } +} + +class SFSortByBegin implements Comparator +{ + @Override + public int compare(SequenceFeature a, SequenceFeature b) + { + return a.getBegin() - b.getBegin(); + } } diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index 6b797d7..944f263 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -1189,9 +1189,10 @@ public class SequenceGroup implements AnnotatedCollectionI { if (consensus.annotations[i] != null) { - if (consensus.annotations[i].description.charAt(0) == '[') + String desc = consensus.annotations[i].description; + if (desc.length() > 1 && desc.charAt(0) == '[') { - seqs.append(consensus.annotations[i].description.charAt(1)); + seqs.append(desc.charAt(1)); } else { diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index 4e3e12d..48615f0 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -21,8 +21,10 @@ package jalview.datamodel; import jalview.datamodel.features.SequenceFeaturesI; +import jalview.util.MapList; import java.util.BitSet; +import java.util.Iterator; import java.util.List; import java.util.Vector; @@ -226,6 +228,13 @@ public interface SequenceI extends ASequenceI public int[] gapMap(); /** + * Build a bitset corresponding to sequence gaps + * + * @return a BitSet where set values correspond to gaps in the sequence + */ + public BitSet gapBitset(); + + /** * Returns an int array where indices correspond to each position in sequence * char array and the element value gives the result of findPosition for that * index in the sequence. @@ -526,5 +535,45 @@ public interface SequenceI extends ASequenceI * @param c1 * @param c2 */ - int replace(char c1, char c2); + public int replace(char c1, char c2); + + /** + * Answers the GeneLociI, or null if not known + * + * @return + */ + GeneLociI getGeneLoci(); + + /** + * Sets the mapping to gene loci for the sequence + * + * @param speciesId + * @param assemblyId + * @param chromosomeId + * @param map + */ + void setGeneLoci(String speciesId, String assemblyId, + String chromosomeId, MapList map); + + + /** + * Returns the sequence string constructed from the substrings of a sequence + * defined by the int[] ranges provided by an iterator. E.g. the iterator + * could iterate over all visible regions of the alignment + * + * @param it + * the iterator to use + * @return a String corresponding to the sequence + */ + public String getSequenceStringFromIterator(Iterator it); + + /** + * Locate the first position in this sequence which is not contained in an + * iterator region. If no such position exists, return 0 + * + * @param it + * iterator over regions + * @return first residue not contained in regions + */ + public int firstResidueOutsideIterator(Iterator it); } diff --git a/src/jalview/datamodel/StartRegionIterator.java b/src/jalview/datamodel/StartRegionIterator.java new file mode 100644 index 0000000..c21f04a --- /dev/null +++ b/src/jalview/datamodel/StartRegionIterator.java @@ -0,0 +1,143 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * An iterator which iterates over visible start positions of hidden column + * regions in a range. + */ +public class StartRegionIterator implements Iterator +{ + // start position to iterate from + private int start; + + // end position to iterate to + private int end; + + // current index in hiddenColumns + private int currentPosition = 0; + + // local copy or reference to hiddenColumns + private List positions = null; + + /** + * Construct an iterator over hiddenColums bounded at [lowerBound,upperBound] + * + * @param lowerBound + * lower bound to iterate from + * @param upperBound + * upper bound to iterate to + * @param useCopyCols + * whether to make a local copy of hiddenColumns for iteration (set + * to true if calling from outwith the HiddenColumns class) + */ + StartRegionIterator(int lowerBound, int upperBound, + List hiddenColumns) + { + this(null, lowerBound, upperBound, hiddenColumns); + } + + /** + * Construct an iterator over hiddenColums bounded at [lowerBound,upperBound] + * + * @param pos + * a hidden cursor position to start from - may be null + * @param lowerBound + * lower bound to iterate from - will be ignored if pos != null + * @param upperBound + * upper bound to iterate to + * @param hiddenColumns + * the hidden columns collection to use + */ + StartRegionIterator(HiddenCursorPosition pos, int lowerBound, + int upperBound, List hiddenColumns) + { + start = lowerBound; + end = upperBound; + + if (hiddenColumns != null) + { + positions = new ArrayList<>(hiddenColumns.size()); + + // navigate to start, keeping count of hidden columns + int i = 0; + int hiddenSoFar = 0; + + if (pos != null) + { + // use the cursor position provided + i = pos.getRegionIndex(); + hiddenSoFar = pos.getHiddenSoFar(); + } + else + { + // navigate to start + while ((i < hiddenColumns.size()) + && (hiddenColumns.get(i)[0] < start + hiddenSoFar)) + { + int[] region = hiddenColumns.get(i); + hiddenSoFar += region[1] - region[0] + 1; + i++; + } + } + + // iterate from start to end, adding start positions of each + // hidden region. Positions are visible columns count, not absolute + while (i < hiddenColumns.size() + && (hiddenColumns.get(i)[0] <= end + hiddenSoFar)) + { + int[] region = hiddenColumns.get(i); + positions.add(region[0] - hiddenSoFar); + hiddenSoFar += region[1] - region[0] + 1; + i++; + } + } + else + { + positions = new ArrayList<>(); + } + + } + + @Override + public boolean hasNext() + { + return (currentPosition < positions.size()); + } + + /** + * Get next hidden region start position + * + * @return the start position in *visible* coordinates + */ + @Override + public Integer next() + { + int result = positions.get(currentPosition); + currentPosition++; + return result; + } +} + diff --git a/src/jalview/datamodel/VisibleColsCollection.java b/src/jalview/datamodel/VisibleColsCollection.java index e9437a7..4ca51b5 100644 --- a/src/jalview/datamodel/VisibleColsCollection.java +++ b/src/jalview/datamodel/VisibleColsCollection.java @@ -32,17 +32,17 @@ public class VisibleColsCollection implements AlignmentColsCollectionI HiddenColumns hidden; - public VisibleColsCollection(int s, int e, AlignmentI al) + public VisibleColsCollection(int s, int e, HiddenColumns h) { start = s; end = e; - hidden = al.getHiddenColumns(); + hidden = h; } @Override public Iterator iterator() { - return new VisibleColsIterator(start, end, hidden); + return hidden.getVisibleColsIterator(start, end); } @Override diff --git a/src/jalview/datamodel/VisibleColsIterator.java b/src/jalview/datamodel/VisibleColsIterator.java deleted file mode 100644 index 9de468d..0000000 --- a/src/jalview/datamodel/VisibleColsIterator.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.datamodel; - -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -/** - * An iterator which iterates over all visible columns in an alignment - * - * @author kmourao - * - */ -public class VisibleColsIterator implements Iterator -{ - private int last; - - private int current; - - private int next; - - private List hidden; - - private int lasthiddenregion; - - public VisibleColsIterator(int firstcol, int lastcol, - HiddenColumns hiddenCols) - { - last = lastcol; - current = firstcol; - next = firstcol; - hidden = hiddenCols.getHiddenColumnsCopy(); - lasthiddenregion = -1; - - if (hidden != null) - { - int i = 0; - for (i = 0; i < hidden.size(); ++i) - { - if (current >= hidden.get(i)[0] && current <= hidden.get(i)[1]) - { - // current is hidden, move to right - current = hidden.get(i)[1] + 1; - next = current; - } - if (current < hidden.get(i)[0]) - { - break; - } - } - lasthiddenregion = i - 1; - - for (i = hidden.size() - 1; i >= 0; --i) - { - if (last >= hidden.get(i)[0] && last <= hidden.get(i)[1]) - { - // last is hidden, move to left - last = hidden.get(i)[0] - 1; - } - if (last > hidden.get(i)[1]) - { - break; - } - } - } - } - - @Override - public boolean hasNext() - { - return next <= last; - } - - @Override - public Integer next() - { - if (next > last) - { - throw new NoSuchElementException(); - } - current = next; - if ((hidden != null) && (lasthiddenregion + 1 < hidden.size())) - { - // still some more hidden regions - if (next + 1 < hidden.get(lasthiddenregion + 1)[0]) - { - // next+1 is still before the next hidden region - next++; - } - else if ((next + 1 >= hidden.get(lasthiddenregion + 1)[0]) - && (next + 1 <= hidden.get(lasthiddenregion + 1)[1])) - { - // next + 1 is in the next hidden region - next = hidden.get(lasthiddenregion + 1)[1] + 1; - lasthiddenregion++; - } - } - else - { - // finished with hidden regions, just increment normally - next++; - } - return current; - } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } -} diff --git a/src/jalview/datamodel/VisibleContigsIterator.java b/src/jalview/datamodel/VisibleContigsIterator.java new file mode 100644 index 0000000..0185978 --- /dev/null +++ b/src/jalview/datamodel/VisibleContigsIterator.java @@ -0,0 +1,116 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * An iterator which iterates over visible regions in a range. Provides a + * special "endsAtHidden" indicator to allow callers to determine if the final + * visible column is adjacent to a hidden region. + */ +public class VisibleContigsIterator implements Iterator +{ + private List vcontigs = new ArrayList<>(); + + private int currentPosition = 0; + + private boolean endsAtHidden = false; + + VisibleContigsIterator(int start, int end, + List hiddenColumns) + { + if (hiddenColumns != null && hiddenColumns.size() > 0) + { + int vstart = start; + int hideStart; + int hideEnd; + + for (int[] region : hiddenColumns) + { + endsAtHidden = false; + hideStart = region[0]; + hideEnd = region[1]; + + // navigate to start + if (hideEnd < vstart) + { + continue; + } + if (hideStart > vstart) + { + if (end - 1 > hideStart - 1) + { + int[] contig = new int[] { vstart, hideStart - 1 }; + vcontigs.add(contig); + endsAtHidden = true; + } + else + { + int[] contig = new int[] { vstart, end - 1 }; + vcontigs.add(contig); + } + } + vstart = hideEnd + 1; + + // exit if we're past the end + if (vstart >= end) + { + break; + } + } + + if (vstart < end) + { + int[] contig = new int[] { vstart, end - 1 }; + vcontigs.add(contig); + endsAtHidden = false; + } + } + else + { + int[] contig = new int[] { start, end - 1 }; + vcontigs.add(contig); + } + } + + @Override + public boolean hasNext() + { + return (currentPosition < vcontigs.size()); + } + + @Override + public int[] next() + { + int[] result = vcontigs.get(currentPosition); + currentPosition++; + return result; + } + + public boolean endsAtHidden() + { + return endsAtHidden; + } +} + diff --git a/src/jalview/datamodel/features/FeatureAttributeType.java b/src/jalview/datamodel/features/FeatureAttributeType.java new file mode 100644 index 0000000..fd3069d --- /dev/null +++ b/src/jalview/datamodel/features/FeatureAttributeType.java @@ -0,0 +1,12 @@ +package jalview.datamodel.features; + +/** + * A class to model the datatype of feature attributes. + * + * @author gmcarstairs + * + */ +public enum FeatureAttributeType +{ + String, Integer, Float, Character, Flag; +} diff --git a/src/jalview/datamodel/features/FeatureAttributes.java b/src/jalview/datamodel/features/FeatureAttributes.java new file mode 100644 index 0000000..10249f3 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureAttributes.java @@ -0,0 +1,374 @@ +package jalview.datamodel.features; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +/** + * A singleton class to hold the set of attributes known for each feature type + */ +public class FeatureAttributes +{ + public enum Datatype + { + Character, Number, Mixed + } + + private static FeatureAttributes instance = new FeatureAttributes(); + + /* + * map, by feature type, of a map, by attribute name, of + * attribute description and min-max range (if known) + */ + private Map> attributes; + + /* + * a case-insensitive comparator so that attributes are ordered e.g. + * AC + * af + * CSQ:AFR_MAF + * CSQ:Allele + */ + private Comparator comparator = new Comparator() + { + @Override + public int compare(String[] o1, String[] o2) + { + int i = 0; + while (i < o1.length || i < o2.length) + { + if (o2.length <= i) + { + return o1.length <= i ? 0 : 1; + } + if (o1.length <= i) + { + return -1; + } + int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]); + if (comp != 0) + { + return comp; + } + i++; + } + return 0; // same length and all matched + } + }; + + private class AttributeData + { + /* + * description(s) for this attribute, if known + * (different feature source might have differing descriptions) + */ + List description; + + /* + * minimum value (of any numeric values recorded) + */ + float min = 0f; + + /* + * maximum value (of any numeric values recorded) + */ + float max = 0f; + + /* + * flag is set true if any numeric value is detected for this attribute + */ + boolean hasValue = false; + + Datatype type; + + /** + * Note one instance of this attribute, recording unique, non-null + * descriptions, and the min/max of any numerical values + * + * @param desc + * @param value + */ + void addInstance(String desc, String value) + { + addDescription(desc); + + if (value != null) + { + value = value.trim(); + + /* + * Parse numeric value unless we have previously + * seen text data for this attribute type + */ + if (type == null || type == Datatype.Number) + { + try + { + float f = Float.valueOf(value); + min = hasValue ? Float.min(min, f) : f; + max = hasValue ? Float.max(max, f) : f; + hasValue = true; + type = (type == null || type == Datatype.Number) + ? Datatype.Number + : Datatype.Mixed; + } catch (NumberFormatException e) + { + /* + * non-numeric data: treat attribute as Character (or Mixed) + */ + type = (type == null || type == Datatype.Character) + ? Datatype.Character + : Datatype.Mixed; + min = 0f; + max = 0f; + hasValue = false; + } + } + } + } + + /** + * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded + * @return + */ + public String getDescription() + { + if (description != null && description.size() == 1) + { + return description.get(0); + } + return null; + } + + public Datatype getType() + { + return type; + } + + /** + * Adds the given description to the list of known descriptions (without + * duplication) + * + * @param desc + */ + public void addDescription(String desc) + { + if (desc != null) + { + if (description == null) + { + description = new ArrayList<>(); + } + if (!description.contains(desc)) + { + description.add(desc); + } + } + } + } + + /** + * Answers the singleton instance of this class + * + * @return + */ + public static FeatureAttributes getInstance() + { + return instance; + } + + private FeatureAttributes() + { + attributes = new HashMap<>(); + } + + /** + * Answers the attribute names known for the given feature type, in + * alphabetical order (not case sensitive), or an empty set if no attributes + * are known. An attribute name is typically 'simple' e.g. "AC", but may be + * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes + * + * @param featureType + * @return + */ + public List getAttributes(String featureType) + { + if (!attributes.containsKey(featureType)) + { + return Collections. emptyList(); + } + + return new ArrayList<>(attributes.get(featureType).keySet()); + } + + /** + * Answers true if at least one attribute is known for the given feature type, + * else false + * + * @param featureType + * @return + */ + public boolean hasAttributes(String featureType) + { + if (attributes.containsKey(featureType)) + { + if (!attributes.get(featureType).isEmpty()) + { + return true; + } + } + return false; + } + + /** + * Records the given attribute name and description for the given feature + * type, and updates the min-max for any numeric value + * + * @param featureType + * @param description + * @param value + * @param attName + */ + public void addAttribute(String featureType, String description, + Object value, String... attName) + { + if (featureType == null || attName == null) + { + return; + } + + /* + * if attribute value is a map, drill down one more level to + * record its sub-fields + */ + if (value instanceof Map) + { + for (Entry entry : ((Map) value).entrySet()) + { + String[] attNames = new String[attName.length + 1]; + System.arraycopy(attName, 0, attNames, 0, attName.length); + attNames[attName.length] = entry.getKey().toString(); + addAttribute(featureType, description, entry.getValue(), attNames); + } + return; + } + + String valueAsString = value.toString(); + Map atts = attributes.get(featureType); + if (atts == null) + { + atts = new TreeMap<>(comparator); + attributes.put(featureType, atts); + } + AttributeData attData = atts.get(attName); + if (attData == null) + { + attData = new AttributeData(); + atts.put(attName, attData); + } + attData.addInstance(description, valueAsString); + } + + /** + * Answers the description of the given attribute for the given feature type, + * if known and unique, else null + * + * @param featureType + * @param attName + * @return + */ + public String getDescription(String featureType, String... attName) + { + String desc = null; + Map atts = attributes.get(featureType); + if (atts != null) + { + AttributeData attData = atts.get(attName); + if (attData != null) + { + desc = attData.getDescription(); + } + } + return desc; + } + + /** + * Answers the [min, max] value range of the given attribute for the given + * feature type, if known, else null. Attributes with a mixture of text and + * numeric values are considered text (do not return a min-max range). + * + * @param featureType + * @param attName + * @return + */ + public float[] getMinMax(String featureType, String... attName) + { + Map atts = attributes.get(featureType); + if (atts != null) + { + AttributeData attData = atts.get(attName); + if (attData != null && attData.hasValue) + { + return new float[] { attData.min, attData.max }; + } + } + return null; + } + + /** + * Records the given attribute description for the given feature type + * + * @param featureType + * @param attName + * @param description + */ + public void addDescription(String featureType, String description, + String... attName) + { + if (featureType == null || attName == null) + { + return; + } + + Map atts = attributes.get(featureType); + if (atts == null) + { + atts = new TreeMap<>(comparator); + attributes.put(featureType, atts); + } + AttributeData attData = atts.get(attName); + if (attData == null) + { + attData = new AttributeData(); + atts.put(attName, attData); + } + attData.addDescription(description); + } + + /** + * Answers the datatype of the feature, which is one of Character, Number or + * Mixed (or null if not known), as discovered from values recorded. + * + * @param featureType + * @param attName + * @return + */ + public Datatype getDatatype(String featureType, String... attName) + { + Map atts = attributes.get(featureType); + if (atts != null) + { + AttributeData attData = atts.get(attName); + if (attData != null) + { + return attData.getType(); + } + } + return null; + } +} diff --git a/src/jalview/datamodel/features/FeatureMatcher.java b/src/jalview/datamodel/features/FeatureMatcher.java new file mode 100644 index 0000000..f844141 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcher.java @@ -0,0 +1,417 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; +import jalview.util.MessageManager; +import jalview.util.matcher.Condition; +import jalview.util.matcher.Matcher; +import jalview.util.matcher.MatcherI; + +/** + * An immutable class that models one or more match conditions, each of which is + * applied to the value obtained by lookup given the match key. + *

          + * For example, the value provider could be a SequenceFeature's attributes map, + * and the conditions might be + *

            + *
          • CSQ contains "pathological"
          • + *
          • AND
          • + *
          • AF <= 1.0e-5
          • + *
          + * + * @author gmcarstairs + * + */ +public class FeatureMatcher implements FeatureMatcherI +{ + private static final String SCORE = "Score"; + + private static final String LABEL = "Label"; + + private static final String SPACE = " "; + + private static final String QUOTE = "'"; + + /* + * a dummy matcher that comes in useful for the 'add a filter' gui row + */ + public static final FeatureMatcherI NULL_MATCHER = FeatureMatcher + .byLabel(Condition.values()[0], ""); + + private static final String COLON = ":"; + + /* + * if true, match is against feature description + */ + final private boolean byLabel; + + /* + * if true, match is against feature score + */ + final private boolean byScore; + + /* + * if not null, match is against feature attribute [sub-attribute] + */ + final private String[] key; + + final private MatcherI matcher; + + /** + * A helper method that converts a 'compound' attribute name from its display + * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" } + * + * @param attribute + * @return + */ + public static String[] fromAttributeDisplayName(String attribute) + { + return attribute == null ? null : attribute.split(COLON); + } + + /** + * A helper method that converts a 'compound' attribute name to its display + * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" } + * + * @param attName + * @return + */ + public static String toAttributeDisplayName(String[] attName) + { + return attName == null ? "" : String.join(COLON, attName); + } + + /** + * A factory constructor that converts a stringified object (as output by + * toStableString) to an object instance. Returns null if parsing fails. + *

          + * Leniency in parsing (for manually created feature files): + *

            + *
          • keywords Score and Label, and the condition, are not + * case-sensitive
          • + *
          • quotes around value and pattern are optional if string does not include + * a space
          • + *
          + * + * @param descriptor + * @return + */ + public static FeatureMatcher fromString(final String descriptor) + { + String invalidFormat = "Invalid matcher format: " + descriptor; + + /* + * expect + * value condition pattern + * where value is Label or Space or attributeName or attName1:attName2 + * and pattern is a float value as string, or a text string + * attribute names or patterns may be quoted (must be if include space) + */ + String attName = null; + boolean byScore = false; + boolean byLabel = false; + Condition cond = null; + String pattern = null; + + /* + * parse first field (Label / Score / attribute) + * optionally in quotes (required if attName includes space) + */ + String leftToParse = descriptor; + String firstField = null; + + if (descriptor.startsWith(QUOTE)) + { + // 'Label' / 'Score' / 'attName' + int nextQuotePos = descriptor.indexOf(QUOTE, 1); + if (nextQuotePos == -1) + { + System.err.println(invalidFormat); + return null; + } + firstField = descriptor.substring(1, nextQuotePos); + leftToParse = descriptor.substring(nextQuotePos + 1).trim(); + } + else + { + // Label / Score / attName (unquoted) + int nextSpacePos = descriptor.indexOf(SPACE); + if (nextSpacePos == -1) + { + System.err.println(invalidFormat); + return null; + } + firstField = descriptor.substring(0, nextSpacePos); + leftToParse = descriptor.substring(nextSpacePos + 1).trim(); + } + String lower = firstField.toLowerCase(); + if (lower.startsWith(LABEL.toLowerCase())) + { + byLabel = true; + } + else if (lower.startsWith(SCORE.toLowerCase())) + { + byScore = true; + } + else + { + attName = firstField; + } + + /* + * next field is the comparison condition + * most conditions require a following pattern (optionally quoted) + * although some conditions e.g. Present do not + */ + int nextSpacePos = leftToParse.indexOf(SPACE); + if (nextSpacePos == -1) + { + /* + * no value following condition - only valid for some conditions + */ + cond = Condition.fromString(leftToParse); + if (cond == null || cond.needsAPattern()) + { + System.err.println(invalidFormat); + return null; + } + } + else + { + /* + * condition and pattern + */ + cond = Condition.fromString(leftToParse.substring(0, nextSpacePos)); + leftToParse = leftToParse.substring(nextSpacePos + 1).trim(); + if (leftToParse.startsWith(QUOTE)) + { + // pattern in quotes + if (leftToParse.endsWith(QUOTE)) + { + pattern = leftToParse.substring(1, leftToParse.length() - 1); + } + else + { + // unbalanced quote + System.err.println(invalidFormat); + return null; + } + } + else + { + // unquoted pattern + pattern = leftToParse; + } + } + + /* + * we have parsed out value, condition and pattern + * so can now make the FeatureMatcher + */ + try + { + if (byLabel) + { + return FeatureMatcher.byLabel(cond, pattern); + } + else if (byScore) + { + return FeatureMatcher.byScore(cond, pattern); + } + else + { + String[] attNames = FeatureMatcher + .fromAttributeDisplayName(attName); + return FeatureMatcher.byAttribute(cond, pattern, attNames); + } + } catch (NumberFormatException e) + { + // numeric condition with non-numeric pattern + return null; + } + } + + /** + * A factory constructor method for a matcher that applies its match condition + * to the feature label (description) + * + * @param cond + * @param pattern + * @return + * @throws NumberFormatException + * if an invalid numeric pattern is supplied + */ + public static FeatureMatcher byLabel(Condition cond, String pattern) + { + return new FeatureMatcher(new Matcher(cond, pattern), true, false, + null); + } + + /** + * A factory constructor method for a matcher that applies its match condition + * to the feature score + * + * @param cond + * @param pattern + * @return + * @throws NumberFormatException + * if an invalid numeric pattern is supplied + */ + public static FeatureMatcher byScore(Condition cond, String pattern) + { + return new FeatureMatcher(new Matcher(cond, pattern), false, true, + null); + } + + /** + * A factory constructor method for a matcher that applies its match condition + * to the named feature attribute [and optional sub-attribute] + * + * @param cond + * @param pattern + * @param attName + * @return + * @throws NumberFormatException + * if an invalid numeric pattern is supplied + */ + public static FeatureMatcher byAttribute(Condition cond, String pattern, + String... attName) + { + return new FeatureMatcher(new Matcher(cond, pattern), false, false, + attName); + } + + private FeatureMatcher(Matcher m, boolean forLabel, boolean forScore, + String[] theKey) + { + key = theKey; + matcher = m; + byLabel = forLabel; + byScore = forScore; + } + @Override + public boolean matches(SequenceFeature feature) + { + String value = byLabel ? feature.getDescription() + : (byScore ? String.valueOf(feature.getScore()) + : feature.getValueAsString(key)); + return matcher.matches(value); + } + + @Override + public String[] getAttribute() + { + return key; + } + + @Override + public MatcherI getMatcher() + { + return matcher; + } + + /** + * Answers a string description of this matcher, suitable for display, debugging + * or logging. The format may change in future. + */ + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + if (byLabel) + { + sb.append(MessageManager.getString("label.label")); + } + else if (byScore) + { + sb.append(MessageManager.getString("label.score")); + } + else + { + sb.append(String.join(COLON, key)); + } + + Condition condition = matcher.getCondition(); + sb.append(SPACE).append(condition.toString().toLowerCase()); + if (condition.isNumeric()) + { + sb.append(SPACE).append(matcher.getPattern()); + } + else if (condition.needsAPattern()) + { + sb.append(" '").append(matcher.getPattern()).append(QUOTE); + } + + return sb.toString(); + } + + @Override + public boolean isByLabel() + { + return byLabel; + } + + @Override + public boolean isByScore() + { + return byScore; + } + + @Override + public boolean isByAttribute() + { + return getAttribute() != null; + } + + /** + * {@inheritDoc} The output of this method should be parseable by method + * fromString to restore the original object. + */ + @Override + public String toStableString() + { + StringBuilder sb = new StringBuilder(); + if (byLabel) + { + sb.append(LABEL); // no i18n here unlike toString() ! + } + else if (byScore) + { + sb.append(SCORE); + } + else + { + /* + * enclose attribute name in quotes if it includes space + */ + String displayName = toAttributeDisplayName(key); + if (displayName.contains(SPACE)) + { + sb.append(QUOTE).append(displayName).append(QUOTE); + } + else + { + sb.append(displayName); + } + } + + Condition condition = matcher.getCondition(); + sb.append(SPACE).append(condition.getStableName()); + String pattern = matcher.getPattern(); + if (condition.needsAPattern()) + { + /* + * enclose pattern in quotes if it includes space + */ + if (pattern.contains(SPACE)) + { + sb.append(SPACE).append(QUOTE).append(pattern).append(QUOTE); + } + else + { + sb.append(SPACE).append(pattern); + } + } + + return sb.toString(); + } +} diff --git a/src/jalview/datamodel/features/FeatureMatcherI.java b/src/jalview/datamodel/features/FeatureMatcherI.java new file mode 100644 index 0000000..f1f8585 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcherI.java @@ -0,0 +1,65 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; +import jalview.util.matcher.MatcherI; + +/** + * An interface for an object that can apply a match condition to a + * SequenceFeature object + * + * @author gmcarstairs + */ +public interface FeatureMatcherI +{ + /** + * Answers true if the value provided for this matcher's key passes this + * matcher's match condition + * + * @param feature + * @return + */ + boolean matches(SequenceFeature feature); + + /** + * Answers the attribute key this matcher operates on (or null if match is by + * Label or Score) + * + * @return + */ + String[] getAttribute(); + + /** + * Answers true if match is against feature label (description), else false + * + * @return + */ + boolean isByLabel(); + + /** + * Answers true if match is against feature score, else false + * + * @return + */ + boolean isByScore(); + + /** + * Answers true if match is against a feature attribute (text or range) + * + * @return + */ + boolean isByAttribute(); + + /** + * Answers the match condition that is applied + * + * @return + */ + MatcherI getMatcher(); + + /** + * Answers a string representation of this object suitable for use when + * persisting data, in a format that can be reliably read back. Any changes to + * the format should be backwards compatible. + */ + String toStableString(); +} diff --git a/src/jalview/datamodel/features/FeatureMatcherSet.java b/src/jalview/datamodel/features/FeatureMatcherSet.java new file mode 100644 index 0000000..b51f2f0 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcherSet.java @@ -0,0 +1,294 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; +import jalview.util.MessageManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class that models one or more match conditions, which may be combined with + * AND or OR (but not a mixture) + * + * @author gmcarstairs + */ +public class FeatureMatcherSet implements FeatureMatcherSetI +{ + private static final String OR = "OR"; + + private static final String AND = "AND"; + + private static final String SPACE = " "; + + private static final String CLOSE_BRACKET = ")"; + + private static final String OPEN_BRACKET = "("; + + private static final String OR_I18N = MessageManager + .getString("label.or"); + + private static final String AND_18N = MessageManager + .getString("label.and"); + + List matchConditions; + + boolean andConditions; + + /** + * A factory constructor that converts a stringified object (as output by + * toStableString) to an object instance. + * + * Format: + *
            + *
          • (condition1) AND (condition2) AND (condition3)
          • + *
          • or
          • + *
          • (condition1) OR (condition2) OR (condition3)
          • + *
          + * where OR and AND are not case-sensitive, and may not be mixed. Brackets are + * optional if there is only one condition. + * + * @param descriptor + * @return + * @see FeatureMatcher#fromString(String) + */ + public static FeatureMatcherSet fromString(final String descriptor) + { + String invalid = "Invalid descriptor: " + descriptor; + boolean firstCondition = true; + FeatureMatcherSet result = new FeatureMatcherSet(); + + String leftToParse = descriptor.trim(); + + while (leftToParse.length() > 0) + { + /* + * inspect AND or OR condition, check not mixed + */ + boolean and = true; + if (!firstCondition) + { + int spacePos = leftToParse.indexOf(SPACE); + if (spacePos == -1) + { + // trailing junk after a match condition + System.err.println(invalid); + return null; + } + String conjunction = leftToParse.substring(0, spacePos); + leftToParse = leftToParse.substring(spacePos + 1).trim(); + if (conjunction.equalsIgnoreCase(AND)) + { + and = true; + } + else if (conjunction.equalsIgnoreCase(OR)) + { + and = false; + } + else + { + // not an AND or an OR - invalid + System.err.println(invalid); + return null; + } + } + + /* + * now extract the next condition and AND or OR it + */ + String nextCondition = leftToParse; + if (leftToParse.startsWith(OPEN_BRACKET)) + { + int closePos = leftToParse.indexOf(CLOSE_BRACKET); + if (closePos == -1) + { + System.err.println(invalid); + return null; + } + nextCondition = leftToParse.substring(1, closePos); + leftToParse = leftToParse.substring(closePos + 1).trim(); + } + else + { + leftToParse = ""; + } + + FeatureMatcher fm = FeatureMatcher.fromString(nextCondition); + if (fm == null) + { + System.err.println(invalid); + return null; + } + try + { + if (and) + { + result.and(fm); + } + else + { + result.or(fm); + } + firstCondition = false; + } catch (IllegalStateException e) + { + // thrown if OR and AND are mixed + System.err.println(invalid); + return null; + } + + } + return result; + } + + /** + * Constructor + */ + public FeatureMatcherSet() + { + matchConditions = new ArrayList<>(); + } + + @Override + public boolean matches(SequenceFeature feature) + { + /* + * no conditions matches anything + */ + if (matchConditions.isEmpty()) + { + return true; + } + + /* + * AND until failure + */ + if (andConditions) + { + for (FeatureMatcherI m : matchConditions) + { + if (!m.matches(feature)) + { + return false; + } + } + return true; + } + + /* + * OR until match + */ + for (FeatureMatcherI m : matchConditions) + { + if (m.matches(feature)) + { + return true; + } + } + return false; + } + + @Override + public void and(FeatureMatcherI m) + { + if (!andConditions && matchConditions.size() > 1) + { + throw new IllegalStateException("Can't add an AND to OR conditions"); + } + matchConditions.add(m); + andConditions = true; + } + + @Override + public void or(FeatureMatcherI m) + { + if (andConditions && matchConditions.size() > 1) + { + throw new IllegalStateException("Can't add an OR to AND conditions"); + } + matchConditions.add(m); + andConditions = false; + } + + @Override + public boolean isAnded() + { + return andConditions; + } + + @Override + public Iterable getMatchers() + { + return matchConditions; + } + + /** + * Answers a string representation of this object suitable for display, and + * possibly internationalized. The format is not guaranteed stable and may + * change in future. + */ + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + boolean first = true; + boolean multiple = matchConditions.size() > 1; + for (FeatureMatcherI matcher : matchConditions) + { + if (!first) + { + String joiner = andConditions ? AND_18N : OR_I18N; + sb.append(SPACE).append(joiner.toLowerCase()).append(SPACE); + } + first = false; + if (multiple) + { + sb.append(OPEN_BRACKET).append(matcher.toString()) + .append(CLOSE_BRACKET); + } + else + { + sb.append(matcher.toString()); + } + } + return sb.toString(); + } + + @Override + public boolean isEmpty() + { + return matchConditions == null || matchConditions.isEmpty(); + } + + /** + * {@inheritDoc} The output of this method should be parseable by method + * fromString to restore the original object. + */ + @Override + public String toStableString() + { + StringBuilder sb = new StringBuilder(); + boolean moreThanOne = matchConditions.size() > 1; + boolean first = true; + + for (FeatureMatcherI matcher : matchConditions) + { + if (!first) + { + String joiner = andConditions ? AND : OR; + sb.append(SPACE).append(joiner).append(SPACE); + } + first = false; + if (moreThanOne) + { + sb.append(OPEN_BRACKET).append(matcher.toStableString()) + .append(CLOSE_BRACKET); + } + else + { + sb.append(matcher.toStableString()); + } + } + return sb.toString(); + } + +} diff --git a/src/jalview/datamodel/features/FeatureMatcherSetI.java b/src/jalview/datamodel/features/FeatureMatcherSetI.java new file mode 100644 index 0000000..90c2986 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcherSetI.java @@ -0,0 +1,68 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; + +/** + * An interface to describe a set of one or more feature matchers, where all + * matchers are combined with either AND or OR + * + * @author gmcarstairs + * + */ +public interface FeatureMatcherSetI +{ + /** + * Answers true if the feature provided passes this matcher's match condition + * + * @param feature + * @return + */ + boolean matches(SequenceFeature feature); + + /** + * Adds (ANDs) match condition m to this object's matcher set + * + * @param m + * @throws IllegalStateException + * if an attempt is made to AND to existing OR-ed conditions + */ + void and(FeatureMatcherI m); + + /** + * Answers true if any second condition is AND-ed with this one, false if it + * is OR-ed + * + * @return + */ + boolean isAnded(); + + /** + * Adds (ORs) the given condition to this object's match conditions + * + * @param m + * @throws IllegalStateException + * if an attempt is made to OR to existing AND-ed conditions + */ + void or(FeatureMatcherI m); + + /** + * Answers an iterator over the combined match conditions + * + * @return + */ + Iterable getMatchers(); + + /** + * Answers true if this object contains no conditions + * + * @return + */ + boolean isEmpty(); + + /** + * Answers a string representation of this object suitable for use when + * persisting data, in a format that can be reliably read back. Any changes to + * the format should be backwards compatible. + */ + String toStableString(); +} diff --git a/src/jalview/datamodel/features/FeatureSource.java b/src/jalview/datamodel/features/FeatureSource.java new file mode 100644 index 0000000..a1be1dc --- /dev/null +++ b/src/jalview/datamodel/features/FeatureSource.java @@ -0,0 +1,78 @@ +package jalview.datamodel.features; + +import java.util.HashMap; +import java.util.Map; + +/** + * A class to model one source of feature data, including metadata about + * attributes of features + * + * @author gmcarstairs + * + */ +public class FeatureSource implements FeatureSourceI +{ + private String name; + + private Map attributeNames; + + private Map attributeTypes; + + /** + * Constructor + * + * @param theName + */ + public FeatureSource(String theName) + { + this.name = theName; + attributeNames = new HashMap<>(); + attributeTypes = new HashMap<>(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() + { + return name; + } + + /** + * {@inheritDoc} + */ + @Override + public String getAttributeName(String attributeId) + { + return attributeNames.get(attributeId); + } + + /** + * {@inheritDoc} + */ + @Override + public FeatureAttributeType getAttributeType(String attributeId) + { + return attributeTypes.get(attributeId); + } + + /** + * {@inheritDoc} + */ + @Override + public void setAttributeName(String id, String attName) + { + attributeNames.put(id, attName); + } + + /** + * {@inheritDoc} + */ + @Override + public void setAttributeType(String id, FeatureAttributeType type) + { + attributeTypes.put(id, type); + } + +} diff --git a/src/jalview/datamodel/features/FeatureSourceI.java b/src/jalview/datamodel/features/FeatureSourceI.java new file mode 100644 index 0000000..c873593 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureSourceI.java @@ -0,0 +1,45 @@ +package jalview.datamodel.features; + +public interface FeatureSourceI +{ + /** + * Answers a name for the feature source (not necessarily unique) + * + * @return + */ + String getName(); + + /** + * Answers the 'long name' of an attribute given its id (short name or + * abbreviation), or null if not known + * + * @param attributeId + * @return + */ + String getAttributeName(String attributeId); + + /** + * Sets the 'long name' of an attribute given its id (short name or + * abbreviation). + * + * @param id + * @param name + */ + void setAttributeName(String id, String name); + + /** + * Answers the datatype of the attribute with given id, or null if not known + * + * @param attributeId + * @return + */ + FeatureAttributeType getAttributeType(String attributeId); + + /** + * Sets the datatype of the attribute with given id + * + * @param id + * @param type + */ + void setAttributeType(String id, FeatureAttributeType type); +} diff --git a/src/jalview/datamodel/features/FeatureSources.java b/src/jalview/datamodel/features/FeatureSources.java new file mode 100644 index 0000000..1be1b82 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureSources.java @@ -0,0 +1,58 @@ +package jalview.datamodel.features; + +import java.util.HashMap; +import java.util.Map; + +/** + * A singleton to hold metadata about feature attributes, keyed by a unique + * feature source identifier + * + * @author gmcarstairs + * + */ +public class FeatureSources +{ + private static FeatureSources instance = new FeatureSources(); + + private Map sources; + + /** + * Answers the singleton instance of this class + * + * @return + */ + public static FeatureSources getInstance() + { + return instance; + } + + private FeatureSources() + { + sources = new HashMap<>(); + } + + /** + * Answers the FeatureSource with the given unique identifier, or null if not + * known + * + * @param sourceId + * @return + */ + public FeatureSourceI getSource(String sourceId) + { + return sources.get(sourceId); + } + + /** + * Adds the given source under the given key. This will replace any existing + * source with the same id, it is the caller's responsibility to ensure keys + * are unique if necessary. + * + * @param sourceId + * @param source + */ + public void addSource(String sourceId, FeatureSource source) + { + sources.put(sourceId, source); + } +} diff --git a/src/jalview/datamodel/features/SequenceFeatures.java b/src/jalview/datamodel/features/SequenceFeatures.java index e489e4c..8f965b4 100644 --- a/src/jalview/datamodel/features/SequenceFeatures.java +++ b/src/jalview/datamodel/features/SequenceFeatures.java @@ -87,7 +87,7 @@ public class SequenceFeatures implements SequenceFeaturesI */ // featureStore = Collections // .synchronizedSortedMap(new TreeMap()); - featureStore = new TreeMap(); + featureStore = new TreeMap<>(); } /** @@ -382,9 +382,10 @@ public class SequenceFeatures implements SequenceFeaturesI } /** - * Answers true if the given type is one of the specified sequence ontology - * terms (or a sub-type of one), or if no terms are supplied. Answers false if - * filter terms are specified and the given term does not match any of them. + * Answers true if the given type matches one of the specified terms (or is a + * sub-type of one in the Sequence Ontology), or if no terms are supplied. + * Answers false if filter terms are specified and the given term does not + * match any of them. * * @param type * @param soTerm @@ -399,7 +400,7 @@ public class SequenceFeatures implements SequenceFeaturesI SequenceOntologyI so = SequenceOntologyFactory.getInstance(); for (String term : soTerm) { - if (so.isA(type, term)) + if (type.equals(term) || so.isA(type, term)) { return true; } diff --git a/src/jalview/datamodel/features/SequenceFeaturesI.java b/src/jalview/datamodel/features/SequenceFeaturesI.java index c11031b..ca0283e 100644 --- a/src/jalview/datamodel/features/SequenceFeaturesI.java +++ b/src/jalview/datamodel/features/SequenceFeaturesI.java @@ -82,9 +82,9 @@ public interface SequenceFeaturesI String group, String... type); /** - * Answers a list of all features stored, whose type either matches one of the - * given ontology terms, or is a specialisation of a term in the Sequence - * Ontology. Results are returned in no particular guaranteed order. + * Answers a list of all features stored, whose type either matches, or is a + * specialisation (in the Sequence Ontology) of, one of the given terms. + * Results are returned in no particular order. * * @param ontologyTerm * @return diff --git a/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java b/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java index b1ed275..8bd5652 100644 --- a/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java +++ b/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java @@ -20,6 +20,8 @@ */ package jalview.datamodel.xdb.uniprot; +import java.util.Vector; + /** * A data model class for binding from Uniprot XML via uniprot_mapping.xml */ @@ -27,7 +29,11 @@ public class UniprotFeature { private String type; - private String description; + private String description = null; + + private String original = null; + + private Vector variation = null; private String status; @@ -95,4 +101,24 @@ public class UniprotFeature this.begin = p; this.end = p; } + + public String getOriginal() + { + return original; + } + + public void setOriginal(String original) + { + this.original = original; + } + + public Vector getVariation() + { + return variation; + } + + public void setVariation(Vector variant) + { + this.variation = variant; + } } diff --git a/src/jalview/ext/ensembl/EnsemblCdna.java b/src/jalview/ext/ensembl/EnsemblCdna.java index 952f01e..7384327 100644 --- a/src/jalview/ext/ensembl/EnsemblCdna.java +++ b/src/jalview/ext/ensembl/EnsemblCdna.java @@ -21,9 +21,13 @@ package jalview.ext.ensembl; import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; import jalview.io.gff.SequenceOntologyFactory; import jalview.io.gff.SequenceOntologyI; +import java.util.ArrayList; +import java.util.List; + import com.stevesoft.pat.Regex; /** @@ -109,23 +113,27 @@ public class EnsemblCdna extends EnsemblSeqProxy } /** - * Answers true if the sequence feature type is 'exon' (or a subtype of exon - * in the Sequence Ontology), and the Parent of the feature is the transcript - * we are retrieving + * Answers a list of sequence features (if any) whose type is 'exon' (or a + * subtype of exon in the Sequence Ontology), and whose Parent is the + * transcript we are retrieving */ @Override - protected boolean identifiesSequence(SequenceFeature sf, String accId) + protected List getIdentifyingFeatures(SequenceI seq, + String accId) { - if (SequenceOntologyFactory.getInstance().isA(sf.getType(), - SequenceOntologyI.EXON)) + List result = new ArrayList<>(); + List sfs = seq.getFeatures() + .getFeaturesByOntology(SequenceOntologyI.EXON); + for (SequenceFeature sf : sfs) { String parentFeature = (String) sf.getValue(PARENT); if (("transcript:" + accId).equals(parentFeature)) { - return true; + result.add(sf); } } - return false; + + return result; } /** diff --git a/src/jalview/ext/ensembl/EnsemblCds.java b/src/jalview/ext/ensembl/EnsemblCds.java index 8b2550d..8a71b64 100644 --- a/src/jalview/ext/ensembl/EnsemblCds.java +++ b/src/jalview/ext/ensembl/EnsemblCds.java @@ -102,23 +102,26 @@ public class EnsemblCds extends EnsemblSeqProxy } /** - * Answers true if the sequence feature type is 'CDS' (or a subtype of CDS in - * the Sequence Ontology), and the Parent of the feature is the transcript we - * are retrieving + * Answers a list of sequence features (if any) whose type is 'CDS' (or a + * subtype of CDS in the Sequence Ontology), and whose Parent is the + * transcript we are retrieving */ @Override - protected boolean identifiesSequence(SequenceFeature sf, String accId) + protected List getIdentifyingFeatures(SequenceI seq, + String accId) { - if (SequenceOntologyFactory.getInstance().isA(sf.getType(), - SequenceOntologyI.CDS)) + List result = new ArrayList<>(); + List sfs = seq.getFeatures() + .getFeaturesByOntology(SequenceOntologyI.CDS); + for (SequenceFeature sf : sfs) { String parentFeature = (String) sf.getValue(PARENT); if (("transcript:" + accId).equals(parentFeature)) { - return true; + result.add(sf); } } - return false; + return result; } /** @@ -130,7 +133,7 @@ public class EnsemblCds extends EnsemblSeqProxy protected List getCdsRanges(SequenceI dnaSeq) { int len = dnaSeq.getLength(); - List ranges = new ArrayList(); + List ranges = new ArrayList<>(); ranges.add(new int[] { 1, len }); return ranges; } diff --git a/src/jalview/ext/ensembl/EnsemblData.java b/src/jalview/ext/ensembl/EnsemblData.java new file mode 100644 index 0000000..47fe0fc --- /dev/null +++ b/src/jalview/ext/ensembl/EnsemblData.java @@ -0,0 +1,91 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.ext.ensembl; + +/** + * A data class to model the data and rest version of one Ensembl domain, + * currently for rest.ensembl.org and rest.ensemblgenomes.org + * + * @author gmcarstairs + */ +class EnsemblData +{ + /* + * The http domain this object is holding data values for + */ + String domain; + + /* + * The latest version Jalview has tested for, e.g. "4.5"; a minor version change should be + * ok, a major version change may break stuff + */ + String expectedRestVersion; + + /* + * Major / minor / point version e.g. "4.5.1" + * @see http://rest.ensembl.org/info/rest/?content-type=application/json + */ + String restVersion; + + /* + * data version + * @see http://rest.ensembl.org/info/data/?content-type=application/json + */ + String dataVersion; + + /* + * true when http://rest.ensembl.org/info/ping/?content-type=application/json + * returns response code 200 and not {"error":"Database is unavailable"} + */ + boolean restAvailable; + + /* + * absolute time when availability was last checked + */ + long lastAvailableCheckTime; + + /* + * absolute time when version numbers were last checked + */ + long lastVersionCheckTime; + + // flag set to true if REST major version is not the one expected + boolean restMajorVersionMismatch; + + /* + * absolute time to wait till if we overloaded the REST service + */ + long retryAfter; + + /** + * Constructor given expected REST version number e.g 4.5 or 3.4.3 + * + * @param restExpected + */ + EnsemblData(String theDomain, String restExpected) + { + domain = theDomain; + expectedRestVersion = restExpected; + lastAvailableCheckTime = -1; + lastVersionCheckTime = -1; + } + +} diff --git a/src/jalview/ext/ensembl/EnsemblFeatures.java b/src/jalview/ext/ensembl/EnsemblFeatures.java index cb6f548..582eac6 100644 --- a/src/jalview/ext/ensembl/EnsemblFeatures.java +++ b/src/jalview/ext/ensembl/EnsemblFeatures.java @@ -22,9 +22,11 @@ package jalview.ext.ensembl; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; +import jalview.io.DataSourceType; import jalview.io.FeaturesFile; import jalview.io.FileParse; +import java.io.BufferedReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -84,12 +86,13 @@ class EnsemblFeatures extends EnsemblRestClient // TODO: use a vararg String... for getSequenceRecords instead? List queries = new ArrayList<>(); queries.add(query); - FileParse fp = getSequenceReader(queries); - if (fp == null || !fp.isValid()) + BufferedReader fp = getSequenceReader(queries); + if (fp == null) { return null; } - FeaturesFile fr = new FeaturesFile(fp); + FeaturesFile fr = new FeaturesFile( + new FileParse(fp, null, DataSourceType.URL)); return new Alignment(fr.getSeqsAsArray()); } @@ -140,13 +143,13 @@ class EnsemblFeatures extends EnsemblRestClient * describes the required encoding of the response. */ @Override - protected String getRequestMimeType(boolean multipleIds) + protected String getRequestMimeType() { return "text/x-gff3"; } /** - * Returns the MIME type for GFF3. + * Returns the MIME type for GFF3 */ @Override protected String getResponseMimeType() diff --git a/src/jalview/ext/ensembl/EnsemblGene.java b/src/jalview/ext/ensembl/EnsemblGene.java index c8b59da..36b19e2 100644 --- a/src/jalview/ext/ensembl/EnsemblGene.java +++ b/src/jalview/ext/ensembl/EnsemblGene.java @@ -23,6 +23,8 @@ package jalview.ext.ensembl; import jalview.api.FeatureColourI; import jalview.api.FeatureSettingsModelI; import jalview.datamodel.AlignmentI; +import jalview.datamodel.DBRefEntry; +import jalview.datamodel.GeneLociI; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; @@ -150,8 +152,14 @@ public class EnsemblGene extends EnsemblSeqProxy { continue; } + if (geneAlignment.getHeight() == 1) { + // ensure id has 'correct' case for the Ensembl identifier + geneId = geneAlignment.getSequenceAt(0).getName(); + + findGeneLoci(geneAlignment.getSequenceAt(0), geneId); + getTranscripts(geneAlignment, geneId); } if (al == null) @@ -167,6 +175,67 @@ public class EnsemblGene extends EnsemblSeqProxy } /** + * Calls the /lookup/id REST service, parses the response for gene + * coordinates, and if successful, adds these to the sequence. If this fails, + * fall back on trying to parse the sequence description in case it is in + * Ensembl-gene format e.g. chromosome:GRCh38:17:45051610:45109016:1. + * + * @param seq + * @param geneId + */ + void findGeneLoci(SequenceI seq, String geneId) + { + GeneLociI geneLoci = new EnsemblLookup(getDomain()).getGeneLoci(geneId); + if (geneLoci != null) + { + seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(), + geneLoci.getChromosomeId(), geneLoci.getMap()); + } + else + { + parseChromosomeLocations(seq); + } + } + + /** + * Parses and saves fields of an Ensembl-style description e.g. + * chromosome:GRCh38:17:45051610:45109016:1 + * + * @param seq + */ + boolean parseChromosomeLocations(SequenceI seq) + { + String description = seq.getDescription(); + if (description == null) + { + return false; + } + String[] tokens = description.split(":"); + if (tokens.length == 6 && tokens[0].startsWith(DBRefEntry.CHROMOSOME)) + { + String ref = tokens[1]; + String chrom = tokens[2]; + try + { + int chStart = Integer.parseInt(tokens[3]); + int chEnd = Integer.parseInt(tokens[4]); + boolean forwardStrand = "1".equals(tokens[5]); + String species = ""; // not known here + int[] from = new int[] { seq.getStart(), seq.getEnd() }; + int[] to = new int[] { forwardStrand ? chStart : chEnd, + forwardStrand ? chEnd : chStart }; + MapList map = new MapList(from, to, 1, 1); + seq.setGeneLoci(species, ref, chrom, map); + return true; + } catch (NumberFormatException e) + { + System.err.println("Bad integers in description " + description); + } + } + return false; + } + + /** * Converts a query, which may contain one or more gene, transcript, or * external (to Ensembl) identifiers, into a non-redundant list of gene * identifiers. @@ -360,6 +429,8 @@ public class EnsemblGene extends EnsemblSeqProxy cdna.transferFeatures(gene.getFeatures().getPositionalFeatures(), transcript.getDatasetSequence(), mapping, parentId); + mapTranscriptToChromosome(transcript, gene, mapping); + /* * fetch and save cross-references */ @@ -374,6 +445,42 @@ public class EnsemblGene extends EnsemblSeqProxy } /** + * If the gene has a mapping to chromosome coordinates, derive the transcript + * chromosome regions and save on the transcript sequence + * + * @param transcript + * @param gene + * @param mapping + * the mapping from gene to transcript positions + */ + protected void mapTranscriptToChromosome(SequenceI transcript, + SequenceI gene, MapList mapping) + { + GeneLociI loci = gene.getGeneLoci(); + if (loci == null) + { + return; + } + + MapList geneMapping = loci.getMap(); + + List exons = mapping.getFromRanges(); + List transcriptLoci = new ArrayList<>(); + + for (int[] exon : exons) + { + transcriptLoci.add(geneMapping.locateInTo(exon[0], exon[1])); + } + + List transcriptRange = Arrays.asList(new int[] { + transcript.getStart(), transcript.getEnd() }); + MapList mapList = new MapList(transcriptRange, transcriptLoci, 1, 1); + + transcript.setGeneLoci(loci.getSpeciesId(), loci.getAssemblyId(), + loci.getChromosomeId(), mapList); + } + + /** * Returns the 'transcript_id' property of the sequence feature (or null) * * @param feature @@ -441,23 +548,27 @@ public class EnsemblGene extends EnsemblSeqProxy } /** - * Answers true for a feature of type 'gene' (or a sub-type of gene in the - * Sequence Ontology), whose ID is the accession we are retrieving + * Answers a list of sequence features (if any) whose type is 'gene' (or a + * subtype of gene in the Sequence Ontology), and whose ID is the accession we + * are retrieving */ @Override - protected boolean identifiesSequence(SequenceFeature sf, String accId) + protected List getIdentifyingFeatures(SequenceI seq, + String accId) { - if (SequenceOntologyFactory.getInstance().isA(sf.getType(), - SequenceOntologyI.GENE)) + List result = new ArrayList<>(); + List sfs = seq.getFeatures() + .getFeaturesByOntology(SequenceOntologyI.GENE); + for (SequenceFeature sf : sfs) { // NB features as gff use 'ID'; rest services return as 'id' String id = (String) sf.getValue("ID"); if ((GENE_PREFIX + accId).equalsIgnoreCase(id)) { - return true; + result.add(sf); } } - return false; + return result; } /** @@ -488,17 +599,6 @@ public class EnsemblGene extends EnsemblSeqProxy } /** - * Answers false. This allows an optimisation - a single 'gene' feature is all - * that is needed to identify the positions of the gene on the genomic - * sequence. - */ - @Override - protected boolean isSpliceable() - { - return false; - } - - /** * Override to do nothing as Ensembl doesn't return a protein sequence for a * gene identifier */ diff --git a/src/jalview/ext/ensembl/EnsemblGenome.java b/src/jalview/ext/ensembl/EnsemblGenome.java index bde3c0f..6684e20 100644 --- a/src/jalview/ext/ensembl/EnsemblGenome.java +++ b/src/jalview/ext/ensembl/EnsemblGenome.java @@ -21,6 +21,11 @@ package jalview.ext.ensembl; import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.io.gff.SequenceOntologyI; + +import java.util.ArrayList; +import java.util.List; /** * A client to fetch genomic sequence from Ensembl @@ -94,22 +99,32 @@ public class EnsemblGenome extends EnsemblSeqProxy } /** - * Answers true if the sequence feature type is 'transcript' (or a subtype of - * transcript in the Sequence Ontology), and the ID of the feature is the - * transcript we are retrieving + * Answers a list of sequence features (if any) whose type is 'transcript' (or + * a subtype of transcript in the Sequence Ontology), and whose ID is the + * accession we are retrieving. + *

          + * Note we also include features of type "NMD_transcript_variant", although + * not strictly 'transcript' in the SO, as they used in Ensembl as if they + * were. */ @Override - protected boolean identifiesSequence(SequenceFeature sf, String accId) + protected List getIdentifyingFeatures(SequenceI seq, + String accId) { - if (isTranscript(sf.getType())) + List result = new ArrayList<>(); + List sfs = seq.getFeatures().getFeaturesByOntology( + SequenceOntologyI.TRANSCRIPT, + SequenceOntologyI.NMD_TRANSCRIPT_VARIANT); + for (SequenceFeature sf : sfs) { + // NB features as gff use 'ID'; rest services return as 'id' String id = (String) sf.getValue("ID"); if (("transcript:" + accId).equals(id)) { - return true; + result.add(sf); } } - return false; + return result; } } diff --git a/src/jalview/ext/ensembl/EnsemblGenomes.java b/src/jalview/ext/ensembl/EnsemblGenomes.java index bbd1f26..9fc6a53 100644 --- a/src/jalview/ext/ensembl/EnsemblGenomes.java +++ b/src/jalview/ext/ensembl/EnsemblGenomes.java @@ -20,6 +20,9 @@ */ package jalview.ext.ensembl; +import jalview.bin.Cache; +import jalview.datamodel.DBRefSource; + /** * A class to behave much like EnsemblGene but referencing the ensemblgenomes * domain and data @@ -35,13 +38,15 @@ public class EnsemblGenomes extends EnsemblGene */ public EnsemblGenomes() { - super(ENSEMBL_GENOMES_REST); + super(); + setDomain(Cache.getDefault(ENSEMBL_GENOMES_BASEURL, + DEFAULT_ENSEMBL_GENOMES_BASEURL)); } @Override public String getDbName() { - return "EnsemblGenomes"; + return DBRefSource.ENSEMBLGENOMES; } @Override @@ -56,7 +61,7 @@ public class EnsemblGenomes extends EnsemblGene @Override public String getDbSource() { - return "EnsemblGenomes"; + return DBRefSource.ENSEMBLGENOMES; } } diff --git a/src/jalview/ext/ensembl/EnsemblInfo.java b/src/jalview/ext/ensembl/EnsemblInfo.java index 7668941..fa24f1e 100644 --- a/src/jalview/ext/ensembl/EnsemblInfo.java +++ b/src/jalview/ext/ensembl/EnsemblInfo.java @@ -1,86 +1,173 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ package jalview.ext.ensembl; -/** - * A data class to model the data and rest version of one Ensembl domain, - * currently for rest.ensembl.org and rest.ensemblgenomes.org - * - * @author gmcarstairs - */ -class EnsemblInfo +import jalview.datamodel.AlignmentI; +import jalview.datamodel.DBRefSource; + +import java.io.BufferedReader; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.json.simple.JSONArray; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +public class EnsemblInfo extends EnsemblRestClient { - /* - * The http domain this object is holding data values for - */ - String domain; /* - * The latest version Jalview has tested for, e.g. "4.5"; a minor version change should be - * ok, a major version change may break stuff + * cached results of REST /info/divisions service, currently + *

          +   * { 
          +   *  { "ENSEMBLFUNGI", "http://rest.ensemblgenomes.org"},
          +   *    "ENSEMBLBACTERIA", "http://rest.ensemblgenomes.org"},
          +   *    "ENSEMBLPROTISTS", "http://rest.ensemblgenomes.org"},
          +   *    "ENSEMBLMETAZOA", "http://rest.ensemblgenomes.org"},
          +   *    "ENSEMBLPLANTS",  "http://rest.ensemblgenomes.org"},
          +   *    "ENSEMBL", "http://rest.ensembl.org" }
          +   *  }
          +   * 
          + * The values for EnsemblGenomes are retrieved by a REST call, that for + * Ensembl is added programmatically for convenience of lookup */ - String expectedRestVersion; + private static Map divisions; - /* - * Major / minor / point version e.g. "4.5.1" - * @see http://rest.ensembl.org/info/rest/?content-type=application/json - */ - String restVersion; + @Override + public String getDbName() + { + return "ENSEMBL"; + } - /* - * data version - * @see http://rest.ensembl.org/info/data/?content-type=application/json - */ - String dataVersion; + @Override + public AlignmentI getSequenceRecords(String queries) throws Exception + { + return null; + } - /* - * true when http://rest.ensembl.org/info/ping/?content-type=application/json - * returns response code 200 and not {"error":"Database is unavailable"} + @Override + protected URL getUrl(List ids) throws MalformedURLException + { + return null; + } + + @Override + protected boolean useGetRequest() + { + return true; + } + + /** + * Answers the domain (http://rest.ensembl.org or + * http://rest.ensemblgenomes.org) for the given division, or null if not + * recognised by Ensembl. + * + * @param division + * @return */ - boolean restAvailable; + public String getDomain(String division) + { + if (divisions == null) + { + fetchDivisions(); + } + return divisions.get(division.toUpperCase()); + } - /* - * absolute time when availability was last checked + /** + * On first request only, populate the lookup map by fetching the list of + * divisions known to EnsemblGenomes. */ - long lastAvailableCheckTime; + void fetchDivisions() + { + divisions = new HashMap<>(); - /* - * absolute time when version numbers were last checked + /* + * for convenience, pre-fill ensembl.org as the domain for "ENSEMBL" + */ + divisions.put(DBRefSource.ENSEMBL.toUpperCase(), ensemblDomain); + + BufferedReader br = null; + try + { + URL url = getDivisionsUrl(ensemblGenomesDomain); + if (url != null) + { + br = getHttpResponse(url, null); + } + parseResponse(br, ensemblGenomesDomain); + } catch (IOException e) + { + // ignore + } finally + { + if (br != null) + { + try + { + br.close(); + } catch (IOException e) + { + // ignore + } + } + } + } + + /** + * Parses the JSON response to /info/divisions, and add each to the lookup map + * + * @param br + * @param domain */ - long lastVersionCheckTime; + void parseResponse(BufferedReader br, String domain) + { + JSONParser jp = new JSONParser(); - // flag set to true if REST major version is not the one expected - boolean restMajorVersionMismatch; + try + { + JSONArray parsed = (JSONArray) jp.parse(br); + + Iterator rvals = parsed.iterator(); + while (rvals.hasNext()) + { + String division = rvals.next().toString(); + divisions.put(division.toUpperCase(), domain); + } + } catch (IOException | ParseException | NumberFormatException e) + { + // ignore + } + } /** - * Constructor given expected REST version number e.g 4.5 or 3.4.3 + * Constructs the URL for the EnsemblGenomes /info/divisions REST service + * @param domain TODO * - * @param restExpected + * @return + * @throws MalformedURLException */ - EnsemblInfo(String theDomain, String restExpected) + URL getDivisionsUrl(String domain) throws MalformedURLException { - domain = theDomain; - expectedRestVersion = restExpected; - lastAvailableCheckTime = -1; - lastVersionCheckTime = -1; + return new URL(domain + + "/info/divisions?content-type=application/json"); } + /** + * Returns the set of 'divisions' recognised by Ensembl or EnsemblGenomes + * + * @return + */ + public Set getDivisions() { + if (divisions == null) + { + fetchDivisions(); + } + + return divisions.keySet(); + } } diff --git a/src/jalview/ext/ensembl/EnsemblLookup.java b/src/jalview/ext/ensembl/EnsemblLookup.java index 92763a1..c6b794a 100644 --- a/src/jalview/ext/ensembl/EnsemblLookup.java +++ b/src/jalview/ext/ensembl/EnsemblLookup.java @@ -20,13 +20,17 @@ */ package jalview.ext.ensembl; +import jalview.bin.Cache; import jalview.datamodel.AlignmentI; +import jalview.datamodel.GeneLociI; +import jalview.util.MapList; import java.io.BufferedReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.json.simple.JSONObject; @@ -34,13 +38,16 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; /** - * A client for the Ensembl lookup REST endpoint, used to find the gene - * identifier given a gene, transcript or protein identifier. + * A client for the Ensembl /lookup REST endpoint, used to find the gene + * identifier given a gene, transcript or protein identifier, or to extract the + * species or chromosomal coordinates from the same service response * * @author gmcarstairs */ public class EnsemblLookup extends EnsemblRestClient { + private static final String SPECIES = "species"; + /** * Default constructor (to use rest.ensembl.org) */ @@ -110,40 +117,100 @@ public class EnsemblLookup extends EnsemblRestClient return true; } - @Override - protected String getRequestMimeType(boolean multipleIds) + /** + * Returns the gene id related to the given identifier (which may be for a + * gene, transcript or protein), or null if none is found + * + * @param identifier + * @return + */ + public String getGeneId(String identifier) { - return "application/json"; + return getGeneId(identifier, null); } - @Override - protected String getResponseMimeType() + /** + * Returns the gene id related to the given identifier (which may be for a + * gene, transcript or protein), or null if none is found + * + * @param identifier + * @param objectType + * @return + */ + public String getGeneId(String identifier, String objectType) { - return "application/json"; + return parseGeneId(getResult(identifier, objectType)); } /** - * Returns the gene id related to the given identifier, which may be for a - * gene, transcript or protein + * Parses the JSON response and returns the gene identifier, or null if not + * found. If the returned object_type is Gene, returns the id, if Transcript + * returns the Parent. If it is Translation (peptide identifier), then the + * Parent is the transcript identifier, so we redo the search with this value. * - * @param identifier + * @param br * @return */ - public String getGeneId(String identifier) + protected String parseGeneId(JSONObject val) { - return getGeneId(identifier, null); + if (val == null) + { + return null; + } + String geneId = null; + String type = val.get(OBJECT_TYPE).toString(); + if (OBJECT_TYPE_GENE.equalsIgnoreCase(type)) + { + // got the gene - just returns its id + geneId = val.get(JSON_ID).toString(); + } + else if (OBJECT_TYPE_TRANSCRIPT.equalsIgnoreCase(type)) + { + // got the transcript - return its (Gene) Parent + geneId = val.get(PARENT).toString(); + } + else if (OBJECT_TYPE_TRANSLATION.equalsIgnoreCase(type)) + { + // got the protein - get its Parent, restricted to type Transcript + String transcriptId = val.get(PARENT).toString(); + geneId = getGeneId(transcriptId, OBJECT_TYPE_TRANSCRIPT); + } + + return geneId; } /** - * Calls the Ensembl lookup REST endpoint and retrieves the 'Parent' for the + * Calls the Ensembl lookup REST endpoint and retrieves the 'species' for the * given identifier, or null if not found * * @param identifier + * @return + */ + public String getSpecies(String identifier) + { + String species = null; + JSONObject json = getResult(identifier, null); + if (json != null) + { + Object o = json.get(SPECIES); + if (o != null) + { + species = o.toString(); + } + } + return species; + } + + /** + * Calls the /lookup/id rest service and returns the response as a JSONObject, + * or null if any error + * + * @param identifier * @param objectType * (optional) * @return */ - public String getGeneId(String identifier, String objectType) + protected JSONObject getResult(String identifier, String objectType) { List ids = Arrays.asList(new String[] { identifier }); @@ -151,14 +218,16 @@ public class EnsemblLookup extends EnsemblRestClient try { URL url = getUrl(identifier, objectType); + if (url != null) { br = getHttpResponse(url, ids); } - return br == null ? null : parseResponse(br); - } catch (IOException e) + return br == null ? null : (JSONObject) (new JSONParser().parse(br)); + } catch (IOException | ParseException e) { - // ignore + System.err.println("Error parsing " + identifier + " lookup response " + + e.getMessage()); return null; } finally { @@ -176,44 +245,82 @@ public class EnsemblLookup extends EnsemblRestClient } /** - * Parses the JSON response and returns the gene identifier, or null if not - * found. If the returned object_type is Gene, returns the id, if Transcript - * returns the Parent. If it is Translation (peptide identifier), then the - * Parent is the transcript identifier, so we redo the search with this value. + * Calls the /lookup/id rest service for the given id, and if successful, + * parses and returns the gene's chromosomal coordinates * - * @param br + * @param geneId * @return - * @throws IOException */ - protected String parseResponse(BufferedReader br) throws IOException + public GeneLociI getGeneLoci(String geneId) { - String geneId = null; - JSONParser jp = new JSONParser(); + return parseGeneLoci(getResult(geneId, OBJECT_TYPE_GENE)); + } + + /** + * Parses the /lookup/id response for species, asssembly_name, + * seq_region_name, start, end and returns an object that wraps them, or null + * if unsuccessful + * + * @param json + * @return + */ + GeneLociI parseGeneLoci(JSONObject json) + { + if (json == null) + { + return null; + } + try { - JSONObject val = (JSONObject) jp.parse(br); - String type = val.get(OBJECT_TYPE).toString(); - if (OBJECT_TYPE_GENE.equalsIgnoreCase(type)) - { - // got the gene - just returns its id - geneId = val.get(ID).toString(); - } - else if (OBJECT_TYPE_TRANSCRIPT.equalsIgnoreCase(type)) - { - // got the transcript - return its (Gene) Parent - geneId = val.get(PARENT).toString(); - } - else if (OBJECT_TYPE_TRANSLATION.equalsIgnoreCase(type)) + final String species = json.get("species").toString(); + final String assembly = json.get("assembly_name").toString(); + final String chromosome = json.get("seq_region_name").toString(); + String strand = json.get("strand").toString(); + int start = Integer.parseInt(json.get("start").toString()); + int end = Integer.parseInt(json.get("end").toString()); + int fromEnd = end - start + 1; + boolean reverseStrand = "-1".equals(strand); + int toStart = reverseStrand ? end : start; + int toEnd = reverseStrand ? start : end; + List fromRange = Collections.singletonList(new int[] { 1, + fromEnd }); + List toRange = Collections.singletonList(new int[] { toStart, + toEnd }); + final MapList map = new MapList(fromRange, toRange, 1, 1); + return new GeneLociI() { - // got the protein - get its Parent, restricted to type Transcript - String transcriptId = val.get(PARENT).toString(); - geneId = getGeneId(transcriptId, OBJECT_TYPE_TRANSCRIPT); - } - } catch (ParseException e) + + @Override + public String getSpeciesId() + { + return species == null ? "" : species; + } + + @Override + public String getAssemblyId() + { + return assembly; + } + + @Override + public String getChromosomeId() + { + return chromosome; + } + + @Override + public MapList getMap() + { + return map; + } + }; + } catch (NullPointerException | NumberFormatException e) { - // ignore + Cache.log.error("Error looking up gene loci: " + e.getMessage()); + e.printStackTrace(); } - return geneId; + return null; } } diff --git a/src/jalview/ext/ensembl/EnsemblMap.java b/src/jalview/ext/ensembl/EnsemblMap.java new file mode 100644 index 0000000..f01bd4f --- /dev/null +++ b/src/jalview/ext/ensembl/EnsemblMap.java @@ -0,0 +1,413 @@ +package jalview.ext.ensembl; + +import jalview.datamodel.AlignmentI; +import jalview.datamodel.DBRefSource; +import jalview.datamodel.GeneLociI; +import jalview.util.MapList; + +import java.io.BufferedReader; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +public class EnsemblMap extends EnsemblRestClient +{ + private static final String MAPPED = "mapped"; + + private static final String MAPPINGS = "mappings"; + + private static final String CDS = "cds"; + + private static final String CDNA = "cdna"; + + /** + * Default constructor (to use rest.ensembl.org) + */ + public EnsemblMap() + { + super(); + } + + /** + * Constructor given the target domain to fetch data from + * + * @param + */ + public EnsemblMap(String domain) + { + super(domain); + } + + @Override + public String getDbName() + { + return DBRefSource.ENSEMBL; + } + + @Override + public AlignmentI getSequenceRecords(String queries) throws Exception + { + return null; // not used + } + + /** + * Constructs a URL of the format + * http://rest.ensembl.org/map/human/GRCh38/17:45051610..45109016:1/GRCh37?content-type=application/json + * + * + * @param species + * @param chromosome + * @param fromRef + * @param toRef + * @param startPos + * @param endPos + * @return + * @throws MalformedURLException + */ + protected URL getAssemblyMapUrl(String species, String chromosome, String fromRef, + String toRef, int startPos, int endPos) + throws MalformedURLException + { + /* + * start-end might be reverse strand - present forwards to the service + */ + boolean forward = startPos <= endPos; + int start = forward ? startPos : endPos; + int end = forward ? endPos : startPos; + String strand = forward ? "1" : "-1"; + String url = String.format( + "%s/map/%s/%s/%s:%d..%d:%s/%s?content-type=application/json", + getDomain(), species, fromRef, chromosome, start, end, strand, + toRef); + return new URL(url); + } + + @Override + protected boolean useGetRequest() + { + return true; + } + + @Override + protected URL getUrl(List ids) throws MalformedURLException + { + return null; // not used + } + + /** + * Calls the REST /map service to get the chromosomal coordinates (start/end) + * in 'toRef' that corresponding to the (start/end) queryRange in 'fromRef' + * + * @param species + * @param chromosome + * @param fromRef + * @param toRef + * @param queryRange + * @return + * @see http://rest.ensemblgenomes.org/documentation/info/assembly_map + */ + public int[] getAssemblyMapping(String species, String chromosome, + String fromRef, String toRef, int[] queryRange) + { + URL url = null; + BufferedReader br = null; + + try + { + url = getAssemblyMapUrl(species, chromosome, fromRef, toRef, queryRange[0], + queryRange[1]); + br = getHttpResponse(url, null); + return (parseAssemblyMappingResponse(br)); + } catch (Throwable t) + { + System.out.println("Error calling " + url + ": " + t.getMessage()); + return null; + } finally + { + if (br != null) + { + try + { + br.close(); + } catch (IOException e) + { + // ignore + } + } + } + } + + /** + * Parses the JSON response from the /map/<species>/ REST service. The + * format is (with some fields omitted) + * + *
          +   *  {"mappings": 
          +   *    [{
          +   *       "original": {"end":45109016,"start":45051610},
          +   *       "mapped"  : {"end":43186384,"start":43128978} 
          +   *  }] }
          +   * 
          + * + * @param br + * @return + */ + protected int[] parseAssemblyMappingResponse(BufferedReader br) + { + int[] result = null; + JSONParser jp = new JSONParser(); + + try + { + JSONObject parsed = (JSONObject) jp.parse(br); + JSONArray mappings = (JSONArray) parsed.get(MAPPINGS); + + Iterator rvals = mappings.iterator(); + while (rvals.hasNext()) + { + // todo check for "mapped" + JSONObject val = (JSONObject) rvals.next(); + JSONObject mapped = (JSONObject) val.get(MAPPED); + int start = Integer.parseInt(mapped.get("start").toString()); + int end = Integer.parseInt(mapped.get("end").toString()); + String strand = mapped.get("strand").toString(); + if ("1".equals(strand)) + { + result = new int[] { start, end }; + } + else + { + result = new int[] { end, start }; + } + } + } catch (IOException | ParseException | NumberFormatException e) + { + // ignore + } + return result; + } + + /** + * Calls the REST /map/cds/id service, and returns a DBRefEntry holding the + * returned chromosomal coordinates, or returns null if the call fails + * + * @param division + * e.g. Ensembl, EnsemblMetazoa + * @param accession + * e.g. ENST00000592782, Y55B1AR.1.1 + * @param start + * @param end + * @return + */ + public GeneLociI getCdsMapping(String division, String accession, + int start, int end) + { + return getIdMapping(division, accession, start, end, CDS); + } + + /** + * Calls the REST /map/cdna/id service, and returns a DBRefEntry holding the + * returned chromosomal coordinates, or returns null if the call fails + * + * @param division + * e.g. Ensembl, EnsemblMetazoa + * @param accession + * e.g. ENST00000592782, Y55B1AR.1.1 + * @param start + * @param end + * @return + */ + public GeneLociI getCdnaMapping(String division, String accession, + int start, int end) + { + return getIdMapping(division, accession, start, end, CDNA); + } + + GeneLociI getIdMapping(String division, String accession, int start, + int end, String cdsOrCdna) + { + URL url = null; + BufferedReader br = null; + + try + { + String domain = new EnsemblInfo().getDomain(division); + if (domain != null) + { + url = getIdMapUrl(domain, accession, start, end, cdsOrCdna); + br = getHttpResponse(url, null); + if (br != null) + { + return (parseIdMappingResponse(br, accession, domain)); + } + } + return null; + } catch (Throwable t) + { + System.out.println("Error calling " + url + ": " + t.getMessage()); + return null; + } finally + { + if (br != null) + { + try + { + br.close(); + } catch (IOException e) + { + // ignore + } + } + } + } + + /** + * Constructs a URL to the /map/cds/ or /map/cdna/ REST service. The + * REST call is to either ensembl or ensemblgenomes, as determined from the + * division, e.g. Ensembl or EnsemblProtists. + * + * @param domain + * @param accession + * @param start + * @param end + * @param cdsOrCdna + * @return + * @throws MalformedURLException + */ + URL getIdMapUrl(String domain, String accession, int start, int end, + String cdsOrCdna) throws MalformedURLException + { + String url = String + .format("%s/map/%s/%s/%d..%d?include_original_region=1&content-type=application/json", + domain, cdsOrCdna, accession, start, end); + return new URL(url); + } + + /** + * Parses the JSON response from the /map/cds/ or /map/cdna REST service. The + * format is + * + *
          +   * {"mappings":
          +   *   [
          +   *    {"assembly_name":"TAIR10","end":2501311,"seq_region_name":"1","gap":0,
          +   *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2501114},
          +   *    {"assembly_name":"TAIR10","end":2500815,"seq_region_name":"1","gap":0,
          +   *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2500714}
          +   *   ]
          +   * }
          +   * 
          + * + * @param br + * @param accession + * @param domain + * @return + */ + GeneLociI parseIdMappingResponse(BufferedReader br, String accession, + String domain) + { + JSONParser jp = new JSONParser(); + + try + { + JSONObject parsed = (JSONObject) jp.parse(br); + JSONArray mappings = (JSONArray) parsed.get(MAPPINGS); + + Iterator rvals = mappings.iterator(); + String assembly = null; + String chromosome = null; + int fromEnd = 0; + List regions = new ArrayList<>(); + + while (rvals.hasNext()) + { + JSONObject val = (JSONObject) rvals.next(); + JSONObject original = (JSONObject) val.get("original"); + fromEnd = Integer.parseInt(original.get("end").toString()); + + JSONObject mapped = (JSONObject) val.get(MAPPED); + int start = Integer.parseInt(mapped.get("start").toString()); + int end = Integer.parseInt(mapped.get("end").toString()); + String ass = mapped.get("assembly_name").toString(); + if (assembly != null && !assembly.equals(ass)) + { + System.err + .println("EnsemblMap found multiple assemblies - can't resolve"); + return null; + } + assembly = ass; + String chr = mapped.get("seq_region_name").toString(); + if (chromosome != null && !chromosome.equals(chr)) + { + System.err + .println("EnsemblMap found multiple chromosomes - can't resolve"); + return null; + } + chromosome = chr; + String strand = mapped.get("strand").toString(); + if ("-1".equals(strand)) + { + regions.add(new int[] { end, start }); + } + else + { + regions.add(new int[] { start, end }); + } + } + + /* + * processed all mapped regions on chromosome, assemble the result, + * having first fetched the species id for the accession + */ + final String species = new EnsemblLookup(domain) + .getSpecies(accession); + final String as = assembly; + final String chr = chromosome; + List fromRange = Collections.singletonList(new int[] { 1, + fromEnd }); + final MapList map = new MapList(fromRange, regions, 1, 1); + return new GeneLociI() + { + + @Override + public String getSpeciesId() + { + return species == null ? "" : species; + } + + @Override + public String getAssemblyId() + { + return as; + } + + @Override + public String getChromosomeId() + { + return chr; + } + + @Override + public MapList getMap() + { + return map; + } + }; + } catch (IOException | ParseException | NumberFormatException e) + { + // ignore + } + + return null; + } + +} diff --git a/src/jalview/ext/ensembl/EnsemblProtein.java b/src/jalview/ext/ensembl/EnsemblProtein.java index 99006aa..0280f16 100644 --- a/src/jalview/ext/ensembl/EnsemblProtein.java +++ b/src/jalview/ext/ensembl/EnsemblProtein.java @@ -22,6 +22,10 @@ package jalview.ext.ensembl; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; + +import java.util.ArrayList; +import java.util.List; import com.stevesoft.pat.Regex; @@ -106,10 +110,10 @@ public class EnsemblProtein extends EnsemblSeqProxy } @Override - protected boolean identifiesSequence(SequenceFeature sf, String accId) + protected List getIdentifyingFeatures(SequenceI seq, + String accId) { - // not applicable - protein sequence is not a 'subset' of genomic sequence - return false; + return new ArrayList<>(); } @Override diff --git a/src/jalview/ext/ensembl/EnsemblRestClient.java b/src/jalview/ext/ensembl/EnsemblRestClient.java index b1bc8e5..9e01cc4 100644 --- a/src/jalview/ext/ensembl/EnsemblRestClient.java +++ b/src/jalview/ext/ensembl/EnsemblRestClient.java @@ -20,8 +20,6 @@ */ package jalview.ext.ensembl; -import jalview.io.DataSourceType; -import jalview.io.FileParse; import jalview.util.StringUtils; import java.io.BufferedReader; @@ -66,16 +64,13 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher * @see https://github.com/Ensembl/ensembl-rest/wiki/Change-log * @see http://rest.ensembl.org/info/rest?content-type=application/json */ - private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "6.0"; + private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "7.0"; - private static final String LATEST_ENSEMBL_REST_VERSION = "6.1"; + private static final String LATEST_ENSEMBL_REST_VERSION = "7.0"; private static final String REST_CHANGE_LOG = "https://github.com/Ensembl/ensembl-rest/wiki/Change-log"; - private static Map domainData; - - // @see https://github.com/Ensembl/ensembl-rest/wiki/Output-formats - private static final String PING_URL = "http://rest.ensembl.org/info/ping.json"; + private static Map domainData; private final static long AVAILABILITY_RETEST_INTERVAL = 10000L; // 10 seconds @@ -86,10 +81,10 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher static { domainData = new HashMap<>(); - domainData.put(ENSEMBL_REST, - new EnsemblInfo(ENSEMBL_REST, LATEST_ENSEMBL_REST_VERSION)); - domainData.put(ENSEMBL_GENOMES_REST, new EnsemblInfo( - ENSEMBL_GENOMES_REST, LATEST_ENSEMBLGENOMES_REST_VERSION)); + domainData.put(DEFAULT_ENSEMBL_BASEURL, + new EnsemblData(DEFAULT_ENSEMBL_BASEURL, LATEST_ENSEMBL_REST_VERSION)); + domainData.put(DEFAULT_ENSEMBL_GENOMES_BASEURL, new EnsemblData( + DEFAULT_ENSEMBL_GENOMES_BASEURL, LATEST_ENSEMBLGENOMES_REST_VERSION)); } protected volatile boolean inProgress = false; @@ -99,7 +94,21 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher */ public EnsemblRestClient() { - this(ENSEMBL_REST); + super(); + + /* + * initialise domain info lazily + */ + if (!domainData.containsKey(ensemblDomain)) + { + domainData.put(ensemblDomain, + new EnsemblData(ensemblDomain, LATEST_ENSEMBL_REST_VERSION)); + } + if (!domainData.containsKey(ensemblGenomesDomain)) + { + domainData.put(ensemblGenomesDomain, new EnsemblData( + ensemblGenomesDomain, LATEST_ENSEMBLGENOMES_REST_VERSION)); + } } /** @@ -142,22 +151,28 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher protected abstract boolean useGetRequest(); /** - * Return the desired value for the Content-Type request header - * - * @param multipleIds + * Returns the desired value for the Content-Type request header. Default is + * application/json, override if required to vary this. * * @return * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers */ - protected abstract String getRequestMimeType(boolean multipleIds); + protected String getRequestMimeType() + { + return "application/json"; + } /** - * Return the desired value for the Accept request header + * Return the desired value for the Accept request header. Default is + * application/json, override if required to vary this. * * @return * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers */ - protected abstract String getResponseMimeType(); + protected String getResponseMimeType() + { + return "application/json"; + } /** * Checks Ensembl's REST 'ping' endpoint, and returns true if response @@ -169,11 +184,12 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher boolean checkEnsembl() { BufferedReader br = null; + String pingUrl = getDomain() + "/info/ping" + CONTENT_TYPE_JSON; try { // note this format works for both ensembl and ensemblgenomes // info/ping.json works for ensembl only (March 2016) - URL ping = new URL(getDomain() + "/info/ping" + CONTENT_TYPE_JSON); + URL ping = new URL(pingUrl); /* * expect {"ping":1} if ok @@ -192,7 +208,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher } catch (Throwable t) { System.err.println( - "Error connecting to " + PING_URL + ": " + t.getMessage()); + "Error connecting to " + pingUrl + ": " + t.getMessage()); } finally { if (br != null) @@ -210,25 +226,20 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher } /** - * returns a reader to a Fasta response from the Ensembl sequence endpoint + * Returns a reader to a (Json) response from the Ensembl sequence endpoint. + * If the request failed the return value may be null. * * @param ids * @return * @throws IOException */ - protected FileParse getSequenceReader(List ids) throws IOException + protected BufferedReader getSequenceReader(List ids) + throws IOException { URL url = getUrl(ids); BufferedReader reader = getHttpResponse(url, ids); - if (reader == null) - { - // request failed - return null; - } - FileParse fp = new FileParse(reader, url.toString(), - DataSourceType.URL); - return fp; + return reader; } /** @@ -247,7 +258,8 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher } /** - * Sends the HTTP request and gets the response as a reader + * Sends the HTTP request and gets the response as a reader. Returns null if + * the HTTP response code was not 200. * * @param url * @param ids @@ -256,7 +268,6 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher * in milliseconds * @return * @throws IOException - * if response code was not 200, or other I/O error */ protected BufferedReader getHttpResponse(URL url, List ids, int readTimeout) throws IOException @@ -320,8 +331,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher boolean multipleIds = ids != null && ids.size() > 1; connection.setRequestMethod( multipleIds ? HttpMethod.POST : HttpMethod.GET); - connection.setRequestProperty("Content-Type", - getRequestMimeType(multipleIds)); + connection.setRequestProperty("Content-Type", getRequestMimeType()); connection.setRequestProperty("Accept", getResponseMimeType()); connection.setUseCaches(false); @@ -381,7 +391,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher */ protected boolean isEnsemblAvailable() { - EnsemblInfo info = domainData.get(getDomain()); + EnsemblData info = domainData.get(getDomain()); long now = System.currentTimeMillis(); @@ -455,7 +465,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher */ private void checkEnsemblRestVersion() { - EnsemblInfo info = domainData.get(getDomain()); + EnsemblData info = domainData.get(getDomain()); JSONParser jp = new JSONParser(); URL url = null; diff --git a/src/jalview/ext/ensembl/EnsemblSeqProxy.java b/src/jalview/ext/ensembl/EnsemblSeqProxy.java index b2ebb1a..7b448fd 100644 --- a/src/jalview/ext/ensembl/EnsemblSeqProxy.java +++ b/src/jalview/ext/ensembl/EnsemblSeqProxy.java @@ -28,12 +28,12 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.DBRefSource; import jalview.datamodel.Mapping; +import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.datamodel.features.SequenceFeatures; import jalview.exceptions.JalviewException; -import jalview.io.FastaFile; -import jalview.io.FileParse; +import jalview.io.gff.Gff3Helper; import jalview.io.gff.SequenceOntologyFactory; import jalview.io.gff.SequenceOntologyI; import jalview.util.Comparison; @@ -41,6 +41,7 @@ import jalview.util.DBRefUtils; import jalview.util.IntRangeComparator; import jalview.util.MapList; +import java.io.BufferedReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -49,6 +50,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + /** * Base class for Ensembl sequence fetchers * @@ -57,8 +62,6 @@ import java.util.List; */ public abstract class EnsemblSeqProxy extends EnsemblRestClient { - private static final String ALLELES = "alleles"; - protected static final String NAME = "Name"; protected static final String DESCRIPTION = "description"; @@ -386,50 +389,44 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient inProgress = false; throw new JalviewException("ENSEMBL Rest API not available."); } - FileParse fp = getSequenceReader(ids); - if (fp == null) + BufferedReader br = getSequenceReader(ids); + if (br == null) { return alignment; } - FastaFile fr = new FastaFile(fp); - if (fr.hasWarningMessage()) + List seqs = parseSequenceJson(br); + + if (seqs.isEmpty()) { - System.out.println( - String.format("Warning when retrieving %d ids %s\n%s", - ids.size(), ids.toString(), fr.getWarningMessage())); + throw new IOException("No data returned for " + ids); } - else if (fr.getSeqs().size() != ids.size()) + + if (seqs.size() != ids.size()) { System.out.println(String.format( "Only retrieved %d sequences for %d query strings", - fr.getSeqs().size(), ids.size())); + seqs.size(), ids.size())); } - if (fr.getSeqs().size() == 1 && fr.getSeqs().get(0).getLength() == 0) + if (!seqs.isEmpty()) { - /* - * POST request has returned an empty FASTA file e.g. for invalid id - */ - throw new IOException("No data returned for " + ids); - } - - if (fr.getSeqs().size() > 0) - { - AlignmentI seqal = new Alignment(fr.getSeqsAsArray()); - for (SequenceI sq : seqal.getSequences()) + AlignmentI seqal = new Alignment( + seqs.toArray(new SequenceI[seqs.size()])); + for (SequenceI seq : seqs) { - if (sq.getDescription() == null) + if (seq.getDescription() == null) { - sq.setDescription(getDbName()); + seq.setDescription(getDbName()); } - String name = sq.getName(); + String name = seq.getName(); if (ids.contains(name) || ids.contains(name.replace("ENSP", "ENST"))) { - DBRefEntry dbref = DBRefUtils.parseToDbRef(sq, getDbSource(), + // TODO JAL-3077 use true accession version in dbref + DBRefEntry dbref = DBRefUtils.parseToDbRef(seq, getDbSource(), getEnsemblDataVersion(), name); - sq.addDBRef(dbref); + seq.addDBRef(dbref); } } if (alignment == null) @@ -445,6 +442,49 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient } /** + * Parses a JSON response for a single sequence ID query + * + * @param br + * @return a single jalview.datamodel.Sequence + * @see http://rest.ensembl.org/documentation/info/sequence_id + */ + protected List parseSequenceJson(BufferedReader br) + { + JSONParser jp = new JSONParser(); + List result = new ArrayList<>(); + try + { + /* + * for now, assumes only one sequence returned; refactor if needed + * in future to handle a JSONArray with more than one + */ + final JSONObject val = (JSONObject) jp.parse(br); + Object s = val.get("desc"); + String desc = s == null ? null : s.toString(); + s = val.get("id"); + String id = s == null ? null : s.toString(); + s = val.get("seq"); + String seq = s == null ? null : s.toString(); + Sequence sequence = new Sequence(id, seq); + if (desc != null) + { + sequence.setDescription(desc); + } + // todo JAL-3077 make a DBRefEntry with true accession version + // s = val.get("version"); + // String version = s == null ? "0" : s.toString(); + // DBRefEntry dbref = new DBRefEntry(getDbSource(), version, id); + // sequence.addDBRef(dbref); + result.add(sequence); + } catch (ParseException | IOException e) + { + System.err.println("Error processing JSON response: " + e.toString()); + // ignore + } + return result; + } + + /** * Returns the URL for the REST call * * @return @@ -465,7 +505,8 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient } // @see https://github.com/Ensembl/ensembl-rest/wiki/Output-formats urlstring.append("?type=").append(getSourceEnsemblType().getType()); - urlstring.append(("&Accept=text/x-fasta")); + urlstring.append(("&Accept=application/json")); + urlstring.append(("&Content-Type=application/json")); String objectType = getObjectType(); if (objectType != null) @@ -505,18 +546,6 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient return false; } - @Override - protected String getRequestMimeType(boolean multipleIds) - { - return multipleIds ? "application/json" : "text/x-fasta"; - } - - @Override - protected String getResponseMimeType() - { - return "text/x-fasta"; - } - /** * * @return the configured sequence return type for this source @@ -552,8 +581,8 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient protected MapList getGenomicRangesFromFeatures(SequenceI sourceSequence, String accId, int start) { - List sfs = sourceSequence.getFeatures() - .getPositionalFeatures(); + List sfs = getIdentifyingFeatures(sourceSequence, + accId); if (sfs.isEmpty()) { return null; @@ -570,47 +599,31 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient for (SequenceFeature sf : sfs) { + int strand = sf.getStrand(); + strand = strand == 0 ? 1 : strand; // treat unknown as forward + + if (directionSet && strand != direction) + { + // abort - mix of forward and backward + System.err + .println("Error: forward and backward strand for " + accId); + return null; + } + direction = strand; + directionSet = true; + /* - * accept the target feature type or a specialisation of it - * (e.g. coding_exon for exon) + * add to CDS ranges, semi-sorted forwards/backwards */ - if (identifiesSequence(sf, accId)) + if (strand < 0) { - int strand = sf.getStrand(); - strand = strand == 0 ? 1 : strand; // treat unknown as forward - - if (directionSet && strand != direction) - { - // abort - mix of forward and backward - System.err.println( - "Error: forward and backward strand for " + accId); - return null; - } - direction = strand; - directionSet = true; - - /* - * add to CDS ranges, semi-sorted forwards/backwards - */ - if (strand < 0) - { - regions.add(0, new int[] { sf.getEnd(), sf.getBegin() }); - } - else - { - regions.add(new int[] { sf.getBegin(), sf.getEnd() }); - } - mappedLength += Math.abs(sf.getEnd() - sf.getBegin() + 1); - - if (!isSpliceable()) - { - /* - * 'gene' sequence is contiguous so we can stop as soon as its - * identifying feature has been found - */ - break; - } + regions.add(0, new int[] { sf.getEnd(), sf.getBegin() }); + } + else + { + regions.add(new int[] { sf.getBegin(), sf.getEnd() }); } + mappedLength += Math.abs(sf.getEnd() - sf.getBegin() + 1); } if (regions.isEmpty()) @@ -635,26 +648,18 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient } /** - * Answers true if the sequence being retrieved may occupy discontiguous - * regions on the genomic sequence. - */ - protected boolean isSpliceable() - { - return true; - } - - /** - * Returns true if the sequence feature marks positions of the genomic + * Answers a list of sequence features that mark positions of the genomic * sequence feature which are within the sequence being retrieved. For * example, an 'exon' feature whose parent is the target transcript marks the - * cdna positions of the transcript. + * cdna positions of the transcript. For a gene sequence, this is trivially + * just the 'gene' feature with matching gene id. * - * @param sf + * @param seq * @param accId * @return */ - protected abstract boolean identifiesSequence(SequenceFeature sf, - String accId); + protected abstract List getIdentifyingFeatures( + SequenceI seq, String accId); /** * Transfers the sequence feature to the target sequence, locating its start @@ -708,7 +713,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient */ static void reverseComplementAlleles(SequenceFeature sf) { - final String alleles = (String) sf.getValue(ALLELES); + final String alleles = (String) sf.getValue(Gff3Helper.ALLELES); if (alleles == null) { return; @@ -719,7 +724,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient reverseComplementAllele(complement, allele); } String comp = complement.toString(); - sf.setValue(ALLELES, comp); + sf.setValue(Gff3Helper.ALLELES, comp); sf.setDescription(comp); /* @@ -729,7 +734,8 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient String atts = sf.getAttributes(); if (atts != null) { - atts = atts.replace(ALLELES + "=" + alleles, ALLELES + "=" + comp); + atts = atts.replace(Gff3Helper.ALLELES + "=" + alleles, + Gff3Helper.ALLELES + "=" + comp); sf.setAttributes(atts); } } diff --git a/src/jalview/ext/ensembl/EnsemblSequenceFetcher.java b/src/jalview/ext/ensembl/EnsemblSequenceFetcher.java index c4abb20..9e3fef4 100644 --- a/src/jalview/ext/ensembl/EnsemblSequenceFetcher.java +++ b/src/jalview/ext/ensembl/EnsemblSequenceFetcher.java @@ -20,6 +20,7 @@ */ package jalview.ext.ensembl; +import jalview.bin.Cache; import jalview.datamodel.DBRefSource; import jalview.ws.seqfetcher.DbSourceProxyImpl; @@ -32,6 +33,16 @@ import com.stevesoft.pat.Regex; */ abstract class EnsemblSequenceFetcher extends DbSourceProxyImpl { + // domain properties lookup keys: + protected static final String ENSEMBL_BASEURL = "ENSEMBL_BASEURL"; + + protected static final String ENSEMBL_GENOMES_BASEURL = "ENSEMBL_GENOMES_BASEURL"; + + // domain properties default values: + protected static final String DEFAULT_ENSEMBL_BASEURL = "https://rest.ensembl.org"; + + protected static final String DEFAULT_ENSEMBL_GENOMES_BASEURL = "https://rest.ensemblgenomes.org"; + /* * accepts ENSG/T/E/P with 11 digits * or ENSMUSP or similar for other species @@ -41,9 +52,9 @@ abstract class EnsemblSequenceFetcher extends DbSourceProxyImpl "(ENS([A-Z]{3}|)[GTEP]{1}[0-9]{11}$)" + "|" + "(CCDS[0-9.]{3,}$)"); - protected static final String ENSEMBL_GENOMES_REST = "http://rest.ensemblgenomes.org"; + protected final String ensemblGenomesDomain; - protected static final String ENSEMBL_REST = "http://rest.ensembl.org"; + protected final String ensemblDomain; protected static final String OBJECT_TYPE_TRANSLATION = "Translation"; @@ -53,7 +64,7 @@ abstract class EnsemblSequenceFetcher extends DbSourceProxyImpl protected static final String PARENT = "Parent"; - protected static final String ID = "id"; + protected static final String JSON_ID = "id"; protected static final String OBJECT_TYPE = "object_type"; @@ -68,13 +79,29 @@ abstract class EnsemblSequenceFetcher extends DbSourceProxyImpl constrained, regulatory } - private String domain = ENSEMBL_REST; + private String domain; + + /** + * Constructor + */ + public EnsemblSequenceFetcher() + { + /* + * the default domain names may be overridden in .jalview_properties; + * this allows an easy change from http to https in future if needed + */ + ensemblDomain = Cache.getDefault(ENSEMBL_BASEURL, + DEFAULT_ENSEMBL_BASEURL); + ensemblGenomesDomain = Cache.getDefault(ENSEMBL_GENOMES_BASEURL, + DEFAULT_ENSEMBL_GENOMES_BASEURL); + domain = ensemblDomain; + } @Override public String getDbSource() { // NB ensure Uniprot xrefs are canonicalised from "Ensembl" to "ENSEMBL" - if (ENSEMBL_GENOMES_REST.equals(getDomain())) + if (ensemblGenomesDomain.equals(getDomain())) { return DBRefSource.ENSEMBLGENOMES; } diff --git a/src/jalview/ext/ensembl/EnsemblSymbol.java b/src/jalview/ext/ensembl/EnsemblSymbol.java index e3b6c93..40d6cad 100644 --- a/src/jalview/ext/ensembl/EnsemblSymbol.java +++ b/src/jalview/ext/ensembl/EnsemblSymbol.java @@ -75,7 +75,7 @@ public class EnsemblSymbol extends EnsemblXref while (rvals.hasNext()) { JSONObject val = (JSONObject) rvals.next(); - String id = val.get(ID).toString(); + String id = val.get(JSON_ID).toString(); String type = val.get(TYPE).toString(); if (id != null && GENE.equals(type)) { @@ -150,7 +150,6 @@ public class EnsemblSymbol extends EnsemblXref if (br != null) { String geneId = parseSymbolResponse(br); - System.out.println(url + " returned " + geneId); if (geneId != null && !result.contains(geneId)) { result.add(geneId); diff --git a/src/jalview/ext/ensembl/EnsemblXref.java b/src/jalview/ext/ensembl/EnsemblXref.java index 27c448e..77768a6 100644 --- a/src/jalview/ext/ensembl/EnsemblXref.java +++ b/src/jalview/ext/ensembl/EnsemblXref.java @@ -88,18 +88,6 @@ class EnsemblXref extends EnsemblRestClient return true; } - @Override - protected String getRequestMimeType(boolean multipleIds) - { - return "application/json"; - } - - @Override - protected String getResponseMimeType() - { - return "application/json"; - } - /** * Calls the Ensembl xrefs REST endpoint and retrieves any cross-references * ("primary_id") for the given identifier (Ensembl accession id) and database @@ -113,8 +101,8 @@ class EnsemblXref extends EnsemblRestClient */ public List getCrossReferences(String identifier) { - List result = new ArrayList(); - List ids = new ArrayList(); + List result = new ArrayList<>(); + List ids = new ArrayList<>(); ids.add(identifier); BufferedReader br = null; @@ -163,7 +151,7 @@ class EnsemblXref extends EnsemblRestClient throws IOException { JSONParser jp = new JSONParser(); - List result = new ArrayList(); + List result = new ArrayList<>(); try { JSONArray responses = (JSONArray) jp.parse(br); diff --git a/src/jalview/ext/htsjdk/HtsContigDb.java b/src/jalview/ext/htsjdk/HtsContigDb.java index 37ce625..73d1674 100644 --- a/src/jalview/ext/htsjdk/HtsContigDb.java +++ b/src/jalview/ext/htsjdk/HtsContigDb.java @@ -20,17 +20,13 @@ */ package jalview.ext.htsjdk; -import htsjdk.samtools.SAMSequenceDictionary; -import htsjdk.samtools.SAMSequenceRecord; -import htsjdk.samtools.reference.ReferenceSequence; -import htsjdk.samtools.reference.ReferenceSequenceFile; -import htsjdk.samtools.reference.ReferenceSequenceFileFactory; -import htsjdk.samtools.util.StringUtil; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; import java.io.File; +import java.io.IOException; import java.math.BigInteger; +import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -38,6 +34,15 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import htsjdk.samtools.SAMException; +import htsjdk.samtools.SAMSequenceDictionary; +import htsjdk.samtools.SAMSequenceRecord; +import htsjdk.samtools.reference.FastaSequenceIndexCreator; +import htsjdk.samtools.reference.ReferenceSequence; +import htsjdk.samtools.reference.ReferenceSequenceFile; +import htsjdk.samtools.reference.ReferenceSequenceFileFactory; +import htsjdk.samtools.util.StringUtil; + /** * a source of sequence data accessed via the HTSJDK * @@ -46,14 +51,25 @@ import java.util.Set; */ public class HtsContigDb { - private String name; private File dbLocation; private htsjdk.samtools.reference.ReferenceSequenceFile refFile = null; - public HtsContigDb(String name, File descriptor) throws Exception + public static void createFastaSequenceIndex(Path path, boolean overwrite) + throws IOException + { + try + { + FastaSequenceIndexCreator.create(path, overwrite); + } catch (SAMException e) + { + throw new IOException(e.getMessage()); + } + } + + public HtsContigDb(String name, File descriptor) { if (descriptor.isFile()) { @@ -63,7 +79,21 @@ public class HtsContigDb initSource(); } - private void initSource() throws Exception + public void close() + { + if (refFile != null) + { + try + { + refFile.close(); + } catch (IOException e) + { + // ignore + } + } + } + + private void initSource() { if (refFile != null) { @@ -142,8 +172,8 @@ public class HtsContigDb final ReferenceSequenceFile refSeqFile = ReferenceSequenceFileFactory .getReferenceSequenceFile(f, truncate); ReferenceSequence refSeq; - List ret = new ArrayList(); - Set sequenceNames = new HashSet(); + List ret = new ArrayList<>(); + Set sequenceNames = new HashSet<>(); for (int numSequences = 0; (refSeq = refSeqFile .nextSequence()) != null; ++numSequences) { @@ -220,14 +250,29 @@ public class HtsContigDb // ///// end of hts bits. - SequenceI getSequenceProxy(String id) + /** + * Reads the contig with the given id and returns as a Jalview SequenceI object. + * Note the database must be indexed for this operation to succeed. + * + * @param id + * @return + */ + public SequenceI getSequenceProxy(String id) { - if (!isValid()) + if (!isValid() || !refFile.isIndexed()) { + System.err.println( + "Cannot read contig as file is invalid or not indexed"); return null; } ReferenceSequence sseq = refFile.getSequence(id); return new Sequence(sseq.getName(), new String(sseq.getBases())); } + + public boolean isIndexed() + { + return refFile != null && refFile.isIndexed(); + } + } diff --git a/src/jalview/ext/htsjdk/VCFReader.java b/src/jalview/ext/htsjdk/VCFReader.java new file mode 100644 index 0000000..14c057f --- /dev/null +++ b/src/jalview/ext/htsjdk/VCFReader.java @@ -0,0 +1,214 @@ +package jalview.ext.htsjdk; + +import htsjdk.samtools.util.CloseableIterator; +import htsjdk.variant.variantcontext.VariantContext; +import htsjdk.variant.vcf.VCFFileReader; +import htsjdk.variant.vcf.VCFHeader; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; + +/** + * A thin wrapper for htsjdk classes to read either plain, or compressed, or + * compressed and indexed VCF files + */ +public class VCFReader implements Closeable, Iterable +{ + private static final String GZ = "gz"; + + private static final String TBI_EXTENSION = ".tbi"; + + private boolean indexed; + + private VCFFileReader reader; + + /** + * Constructor given a raw or compressed VCF file or a (tabix) index file + *

          + * For now, file type is inferred from its suffix: .gz or .bgz for compressed + * data, .tbi for an index file, anything else is assumed to be plain text + * VCF. + * + * @param f + * @throws IOException + */ + public VCFReader(String filePath) throws IOException + { + if (filePath.endsWith(GZ)) + { + if (new File(filePath + TBI_EXTENSION).exists()) + { + indexed = true; + } + } + else if (filePath.endsWith(TBI_EXTENSION)) + { + indexed = true; + filePath = filePath.substring(0, filePath.length() - 4); + } + + reader = new VCFFileReader(new File(filePath), indexed); + } + + @Override + public void close() throws IOException + { + if (reader != null) + { + reader.close(); + } + } + + /** + * Returns an iterator over VCF variants in the file. The client should call + * close() on the iterator when finished with it. + */ + @Override + public CloseableIterator iterator() + { + return reader == null ? null : reader.iterator(); + } + + /** + * Queries for records overlapping the region specified. Note that this method + * is performant if the VCF file is indexed, and may be very slow if it is + * not. + *

          + * Client code should call close() on the iterator when finished with it. + * + * @param chrom + * the chromosome to query + * @param start + * query interval start + * @param end + * query interval end + * @return + */ + public CloseableIterator query(final String chrom, + final int start, final int end) + { + if (reader == null) { + return null; + } + if (indexed) + { + return reader.query(chrom, start, end); + } + else + { + return queryUnindexed(chrom, start, end); + } + } + + /** + * Returns an iterator over variant records read from a flat file which + * overlap the specified chromosomal positions. Call close() on the iterator + * when finished with it! + * + * @param chrom + * @param start + * @param end + * @return + */ + protected CloseableIterator queryUnindexed( + final String chrom, final int start, final int end) + { + final CloseableIterator it = reader.iterator(); + + return new CloseableIterator() + { + boolean atEnd = false; + + // prime look-ahead buffer with next matching record + private VariantContext next = findNext(); + + private VariantContext findNext() + { + if (atEnd) + { + return null; + } + VariantContext variant = null; + while (it.hasNext()) + { + variant = it.next(); + int vstart = variant.getStart(); + + if (vstart > end) + { + atEnd = true; + close(); + return null; + } + + int vend = variant.getEnd(); + // todo what is the undeprecated way to get + // the chromosome for the variant? + if (chrom.equals(variant.getChr()) && (vstart <= end) + && (vend >= start)) + { + return variant; + } + } + return null; + } + + @Override + public boolean hasNext() + { + boolean hasNext = !atEnd && (next != null); + if (!hasNext) + { + close(); + } + return hasNext; + } + + @Override + public VariantContext next() + { + /* + * return the next match, and then re-prime + * it with the following one (if any) + */ + VariantContext temp = next; + next = findNext(); + return temp; + } + + @Override + public void remove() + { + // not implemented + } + + @Override + public void close() + { + it.close(); + } + }; + } + + /** + * Returns an object that models the VCF file headers + * + * @return + */ + public VCFHeader getFileHeader() + { + return reader == null ? null : reader.getFileHeader(); + } + + /** + * Answers true if we are processing a tab-indexed VCF file, false if it is a + * plain text (uncompressed) file. + * + * @return + */ + public boolean isIndex() + { + return indexed; + } +} diff --git a/src/jalview/ext/jmol/JalviewJmolBinding.java b/src/jalview/ext/jmol/JalviewJmolBinding.java index 41bc116..a5b1110 100644 --- a/src/jalview/ext/jmol/JalviewJmolBinding.java +++ b/src/jalview/ext/jmol/JalviewJmolBinding.java @@ -478,6 +478,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel lastCommand = command; } + Thread colourby = null; /** * Sends a set of colour commands to the structure viewer * @@ -485,15 +486,28 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel */ @Override protected void colourBySequence( - StructureMappingcommandSet[] colourBySequenceCommands) + final StructureMappingcommandSet[] colourBySequenceCommands) { - for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands) + if (colourby != null) { - for (String cbyseq : cpdbbyseq.commands) + colourby.interrupt(); + colourby = null; + } + colourby = new Thread(new Runnable() + { + @Override + public void run() { - executeWhenReady(cbyseq); + for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands) + { + for (String cbyseq : cpdbbyseq.commands) + { + executeWhenReady(cbyseq); + } + } } - } + }); + colourby.start(); } /** @@ -862,19 +876,30 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel try { // recover PDB filename for the model hovered over. - int _mp = _modelFileNameMap.length - 1, - mnumber = new Integer(mdlId).intValue() - 1; - while (mnumber < _modelFileNameMap[_mp]) + int mnumber = new Integer(mdlId).intValue() - 1; + if (_modelFileNameMap != null) { - _mp--; + int _mp = _modelFileNameMap.length - 1; + + while (mnumber < _modelFileNameMap[_mp]) + { + _mp--; + } + pdbfilename = modelFileNames[_mp]; } - pdbfilename = modelFileNames[_mp]; - if (pdbfilename == null) + else { - pdbfilename = new File(viewer.ms.getModelFileName(mnumber)) - .getAbsolutePath(); - } + if (mnumber >= 0 && mnumber < modelFileNames.length) + { + pdbfilename = modelFileNames[mnumber]; + } + if (pdbfilename == null) + { + pdbfilename = new File(viewer.ms.getModelFileName(mnumber)) + .getAbsolutePath(); + } + } } catch (Exception e) { } @@ -1229,7 +1254,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel return chainNames; } - protected abstract IProgressIndicator getIProgressIndicator(); + protected IProgressIndicator getIProgressIndicator() + { + return null; + } public void notifyNewPickingModeMeasurement(int iatom, String strMeasure) { diff --git a/src/jalview/ext/jmol/JmolCommands.java b/src/jalview/ext/jmol/JmolCommands.java index 6bf7010..8fb0de6 100644 --- a/src/jalview/ext/jmol/JmolCommands.java +++ b/src/jalview/ext/jmol/JmolCommands.java @@ -77,7 +77,6 @@ public class JmolCommands continue; } - int lastPos = -1; for (int s = 0; s < sequence[pdbfnum].length; s++) { for (int sp, m = 0; m < mapping.length; m++) @@ -85,6 +84,7 @@ public class JmolCommands if (mapping[m].getSequence() == sequence[pdbfnum][s] && (sp = al.findIndex(sequence[pdbfnum][s])) > -1) { + int lastPos = StructureMapping.UNASSIGNED_VALUE; SequenceI asp = al.getSequenceAt(sp); for (int r = 0; r < asp.getLength(); r++) { @@ -95,10 +95,22 @@ public class JmolCommands } int pos = mapping[m].getPDBResNum(asp.findPosition(r)); - if (pos < 1 || pos == lastPos) + if (pos == lastPos) { continue; } + if (pos == StructureMapping.UNASSIGNED_VALUE) + { + // terminate current colour op + if (command.length() > 0 + && command.charAt(command.length() - 1) != ';') + { + command.append(";"); + } + // reset lastPos + lastPos = StructureMapping.UNASSIGNED_VALUE; + continue; + } lastPos = pos; @@ -128,7 +140,12 @@ public class JmolCommands // TODO: deal with case when buffer is too large for Jmol to parse // - execute command and flush - command.append(";"); + if (command.length() > 0 + && command.charAt(command.length() - 1) != ';') + { + command.append(";"); + } + if (command.length() > 51200) { // add another chunk diff --git a/src/jalview/ext/jmol/JmolParser.java b/src/jalview/ext/jmol/JmolParser.java index dc3d0ee..2a510a2 100644 --- a/src/jalview/ext/jmol/JmolParser.java +++ b/src/jalview/ext/jmol/JmolParser.java @@ -28,7 +28,6 @@ import jalview.io.DataSourceType; import jalview.io.FileParse; import jalview.io.StructureFile; import jalview.schemes.ResidueProperties; -import jalview.structure.StructureImportSettings; import jalview.util.Format; import jalview.util.MessageManager; @@ -60,6 +59,12 @@ public class JmolParser extends StructureFile implements JmolStatusListener { Viewer viewer = null; + public JmolParser(boolean immediate, String inFile, + DataSourceType sourceType) throws IOException + { + super(immediate, inFile, sourceType); + } + public JmolParser(String inFile, DataSourceType sourceType) throws IOException { @@ -183,7 +188,11 @@ public class JmolParser extends StructureFile implements JmolStatusListener } lastID = tmpatom.resNumIns.trim(); } - xferSettings(); + if (isParseImmediately()) + { + // configure parsing settings from the static singleton + xferSettings(); + } makeResidueList(); makeCaBondList(); @@ -200,7 +209,8 @@ public class JmolParser extends StructureFile implements JmolStatusListener prot.add(chainseq); } - if (StructureImportSettings.isProcessSecondaryStructure()) + // look at local setting for adding secondary tructure + if (predictSecondaryStructure) { createAnnotation(chainseq, chain, ms.at); } diff --git a/src/jalview/fts/core/FTSRestClient.java b/src/jalview/fts/core/FTSRestClient.java index 076e212..f94d455 100644 --- a/src/jalview/fts/core/FTSRestClient.java +++ b/src/jalview/fts/core/FTSRestClient.java @@ -284,7 +284,8 @@ public abstract class FTSRestClient implements FTSRestClientI public boolean equals(Object otherObject) { FTSDataColumnI that = (FTSDataColumnI) otherObject; - return this.getCode().equals(that.getCode()) + return otherObject == null ? false + : this.getCode().equals(that.getCode()) && this.getName().equals(that.getName()) && this.getGroup().equals(that.getGroup()); } diff --git a/src/jalview/fts/core/GFTSPanel.java b/src/jalview/fts/core/GFTSPanel.java index 9802d4b..86710e1 100644 --- a/src/jalview/fts/core/GFTSPanel.java +++ b/src/jalview/fts/core/GFTSPanel.java @@ -92,7 +92,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI protected JTabbedPane tabs = new JTabbedPane(); protected IProgressIndicator progressIndicator; - protected JComboBox cmb_searchTarget = new JComboBox(); + protected JComboBox cmb_searchTarget = new JComboBox<>(); protected JButton btn_ok = new JButton(); @@ -151,7 +151,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI protected int pageLimit; - protected HashSet paginatorCart = new HashSet(); + protected HashSet paginatorCart = new HashSet<>(); private static final int MIN_WIDTH = 670; @@ -293,7 +293,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI private void jbInit() throws Exception { - txt_search = new JvCacheableInputBox(getCacheKey()); + txt_search = new JvCacheableInputBox<>(getCacheKey()); populateCmbSearchTargetOptions(); Integer width = getTempUserPrefs().get("FTSPanel.width") == null ? 800 : getTempUserPrefs().get("FTSPanel.width"); @@ -681,7 +681,8 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI if (tabs != null) { tabs.setOpaque(true); - tabs.insertTab("Free Text Search", null, this, "", 0); + tabs.insertTab(MessageManager.getString("label.free_text_search"), + null, this, "", 0); mainFrame.setContentPane(tabs); tabs.setVisible(true); } @@ -871,7 +872,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI */ public void populateCmbSearchTargetOptions() { - List searchableTargets = new ArrayList(); + List searchableTargets = new ArrayList<>(); try { Collection foundFTSTargets = getFTSRestClient() diff --git a/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java b/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java index 250fba0..262ed86 100644 --- a/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java +++ b/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java @@ -21,6 +21,7 @@ package jalview.fts.service.uniprot; +import jalview.bin.Cache; import jalview.fts.api.FTSData; import jalview.fts.api.FTSDataColumnI; import jalview.fts.api.FTSRestClientI; @@ -44,9 +45,18 @@ import com.sun.jersey.api.client.config.DefaultClientConfig; public class UniProtFTSRestClient extends FTSRestClient { + private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org"; + private static FTSRestClientI instance = null; - public static final String UNIPROT_SEARCH_ENDPOINT = "http://www.uniprot.org/uniprot/?"; + public final String uniprotSearchEndpoint; + + public UniProtFTSRestClient() + { + super(); + uniprotSearchEndpoint = Cache.getDefault("UNIPROT_DOMAIN", + DEFAULT_UNIPROT_DOMAIN) + "/uniprot/?"; + } @Override public FTSRestResponse executeRequest(FTSRestRequest uniportRestRequest) @@ -81,7 +91,7 @@ public class UniProtFTSRestClient extends FTSRestClient } WebResource webResource = null; - webResource = client.resource(UNIPROT_SEARCH_ENDPOINT) + webResource = client.resource(uniprotSearchEndpoint) .queryParam("format", "tab") .queryParam("columns", wantedFields) .queryParam("limit", String.valueOf(responseSize)) @@ -158,7 +168,7 @@ public class UniProtFTSRestClient extends FTSRestClient String[] foundDataRow = uniProtTabDelimittedResponseString.split("\n"); if (foundDataRow != null && foundDataRow.length > 0) { - result = new ArrayList(); + result = new ArrayList<>(); boolean firstRow = true; for (String dataRow : foundDataRow) { diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 298688b..94b38ed 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -81,6 +81,7 @@ import jalview.io.JnetAnnotationMaker; import jalview.io.NewickFile; import jalview.io.ScoreMatrixFile; import jalview.io.TCoffeeScoreFile; +import jalview.io.vcf.VCFLoader; import jalview.jbgui.GAlignFrame; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemes; @@ -839,6 +840,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, AlignmentI al = getViewport().getAlignment(); boolean nucleotide = al.isNucleotide(); + loadVcf.setVisible(nucleotide); showTranslation.setVisible(nucleotide); showReverse.setVisible(nucleotide); showReverseComplement.setVisible(nucleotide); @@ -1390,13 +1392,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override public void exportFeatures_actionPerformed(ActionEvent e) { - new AnnotationExporter().exportFeatures(alignPanel); + new AnnotationExporter(alignPanel).exportFeatures(); } @Override public void exportAnnotations_actionPerformed(ActionEvent e) { - new AnnotationExporter().exportAnnotations(alignPanel); + new AnnotationExporter(alignPanel).exportAnnotations(); } @Override @@ -1826,7 +1828,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override protected void copy_actionPerformed(ActionEvent e) { - System.gc(); if (viewport.getSelectionGroup() == null) { return; @@ -1862,23 +1863,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, return; } - ArrayList hiddenColumns = null; + HiddenColumns hiddenColumns = null; if (viewport.hasHiddenColumns()) { - hiddenColumns = new ArrayList<>(); int hiddenOffset = viewport.getSelectionGroup().getStartRes(); int hiddenCutoff = viewport.getSelectionGroup().getEndRes(); - ArrayList hiddenRegions = viewport.getAlignment() - .getHiddenColumns().getHiddenColumnsCopy(); - for (int[] region : hiddenRegions) - { - if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff) - { - hiddenColumns - .add(new int[] - { region[0] - hiddenOffset, region[1] - hiddenOffset }); - } - } + + // create new HiddenColumns object with copy of hidden regions + // between startRes and endRes, offset by startRes + hiddenColumns = new HiddenColumns( + viewport.getAlignment().getHiddenColumns(), hiddenOffset, + hiddenCutoff, hiddenOffset); } Desktop.jalviewClipboard = new Object[] { seqs, @@ -2207,11 +2202,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, if (Desktop.jalviewClipboard != null && Desktop.jalviewClipboard[2] != null) { - List hc = (List) Desktop.jalviewClipboard[2]; - for (int[] region : hc) - { - af.viewport.hideColumns(region[0], region[1]); - } + HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2]; + af.viewport.setHiddenColumns(hc); } // >>>This is a fix for the moment, until a better solution is @@ -2266,11 +2258,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, if (Desktop.jalviewClipboard != null && Desktop.jalviewClipboard[2] != null) { - List hc = (List) Desktop.jalviewClipboard[2]; - for (int region[] : hc) - { - af.viewport.hideColumns(region[0], region[1]); - } + HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2]; + af.viewport.setHiddenColumns(hc); } // >>>This is a fix for the moment, until a better solution is @@ -3271,6 +3260,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, alignPanel.setOverviewPanel(null); }; }); + if (getKeyListeners().length > 0) + { + frame.addKeyListener(getKeyListeners()[0]); + } alignPanel.setOverviewPanel(overview); } @@ -4258,7 +4251,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, protected void showProductsFor(final SequenceI[] sel, final boolean _odna, final String source) { - new Thread(CrossRefAction.showProductsFor(sel, _odna, source, this)) + new Thread(CrossRefAction.getHandlerFor(sel, _odna, source, this)) .start(); } @@ -4471,17 +4464,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, int assocfiles = 0; if (filesmatched.size() > 0) { - if (Cache.getDefault("AUTOASSOCIATE_PDBANDSEQS", false) - || JvOptionPane.showConfirmDialog(thisaf, - MessageManager.formatMessage( - "label.automatically_associate_structure_files_with_sequences_same_name", - new Object[] - { Integer.valueOf(filesmatched.size()) - .toString() }), - MessageManager.getString( - "label.automatically_associate_structure_files_by_name"), - JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION) - + boolean autoAssociate = Cache.getDefault("AUTOASSOCIATE_PDBANDSEQS", false); + if (!autoAssociate) + { + String msg = MessageManager.formatMessage( + "label.automatically_associate_structure_files_with_sequences_same_name", + new Object[] + { Integer.valueOf(filesmatched.size()) + .toString() }); + String ttl = MessageManager.getString( + "label.automatically_associate_structure_files_by_name"); + int choice = JvOptionPane.showConfirmDialog(thisaf, msg, + ttl, JvOptionPane.YES_NO_OPTION); + autoAssociate = choice == JvOptionPane.YES_OPTION; + } + if (autoAssociate) { for (Object[] fm : filesmatched) { @@ -4507,6 +4504,16 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, alignPanel.paintAlignment(true, false); } } + else + { + /* + * add declined structures as sequences + */ + for (Object[] o : filesmatched) + { + filesnotmatched.add((String) o[0]); + } + } } if (filesnotmatched.size() > 0) { @@ -4639,11 +4646,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, new JnetAnnotationMaker(); JnetAnnotationMaker.add_annotation(predictions, viewport.getAlignment(), 0, false); - SequenceI repseq = viewport.getAlignment().getSequenceAt(0); - viewport.getAlignment().setSeqrep(repseq); - HiddenColumns cs = new HiddenColumns(); - cs.hideInsertionsFor(repseq); - viewport.getAlignment().setHiddenColumns(cs); + viewport.getAlignment().setupJPredAlignment(); isAnnotation = true; } // else if (IdentifyFile.FeaturesFile.equals(format)) @@ -4865,14 +4868,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, MessageManager.getString("option.trim_retrieved_seqs")); trimrs.setToolTipText( MessageManager.getString("label.trim_retrieved_sequences")); - trimrs.setSelected(Cache.getDefault("TRIM_FETCHED_DATASET_SEQS", true)); + trimrs.setSelected( + Cache.getDefault(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES, true)); trimrs.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { trimrs.setSelected(trimrs.isSelected()); - Cache.setProperty("TRIM_FETCHED_DATASET_SEQS", + Cache.setProperty(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES, Boolean.valueOf(trimrs.isSelected()).toString()); }; }); @@ -5301,6 +5305,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { if (avc.createGroup()) { + if (applyAutoAnnotationSettings.isSelected()) + { + alignPanel.updateAnnotation(true, false); + } alignPanel.alignmentChanged(); } } @@ -5585,6 +5593,27 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, new CalculationChooser(AlignFrame.this); } } + + @Override + protected void loadVcf_actionPerformed() + { + JalviewFileChooser chooser = new JalviewFileChooser( + Cache.getProperty("LAST_DIRECTORY")); + chooser.setFileView(new JalviewFileView()); + chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file")); + chooser.setToolTipText(MessageManager.getString("label.load_vcf_file")); + + int value = chooser.showOpenDialog(null); + + if (value == JalviewFileChooser.APPROVE_OPTION) + { + String choice = chooser.getSelectedFile().getPath(); + Cache.setProperty("LAST_DIRECTORY", choice); + SequenceI[] seqs = viewport.getAlignment().getSequencesArray(); + new VCFLoader(choice).loadVCF(seqs, this); + } + + } } class PrintThread extends Thread diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 4d09084..7e77bec 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -22,7 +22,6 @@ package jalview.gui; import jalview.analysis.AlignmentUtils; import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; -import jalview.analysis.TreeModel; import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; import jalview.api.FeatureColourI; @@ -36,7 +35,6 @@ import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.HiddenColumns; -import jalview.datamodel.PDBEntry; import jalview.datamodel.SearchResults; import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceGroup; @@ -58,10 +56,9 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Rectangle; -import java.util.ArrayList; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; -import java.util.Vector; import javax.swing.JInternalFrame; @@ -453,10 +450,10 @@ public class AlignViewport extends AlignmentViewport * area * @return */ - public int[] getViewAsVisibleContigs(boolean selectedRegionOnly) + public Iterator getViewAsVisibleContigs(boolean selectedRegionOnly) { - int[] viscontigs = null; - int start = 0, end = 0; + int start = 0; + int end = 0; if (selectedRegionOnly && selectionGroup != null) { start = selectionGroup.getStartRes(); @@ -466,8 +463,8 @@ public class AlignViewport extends AlignmentViewport { end = alignment.getWidth(); } - viscontigs = alignment.getHiddenColumns().getVisibleContigs(start, end); - return viscontigs; + return (alignment.getHiddenColumns().getVisContigsIterator(start, end, + false)); } /** @@ -604,7 +601,7 @@ public class AlignViewport extends AlignmentViewport return validCharWidth; } - private Hashtable calcIdParams = new Hashtable(); + private Hashtable calcIdParams = new Hashtable<>(); public AutoCalcSetting getCalcIdSettingsFor(String calcId) { diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index 3a1dbe8..60ef480 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -37,7 +37,6 @@ import jalview.schemes.ResidueProperties; import jalview.structure.StructureSelectionManager; import jalview.util.Comparison; import jalview.util.MessageManager; -import jalview.util.Platform; import jalview.viewmodel.ViewportListenerI; import jalview.viewmodel.ViewportRanges; @@ -48,7 +47,7 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; -import java.awt.Insets; +import java.awt.Graphics2D; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ComponentAdapter; @@ -419,8 +418,8 @@ public class AlignmentPanel extends GAlignmentPanel implements if (av.hasHiddenColumns()) { HiddenColumns hidden = av.getAlignment().getHiddenColumns(); - start = hidden.findColumnPosition(start); - end = hidden.findColumnPosition(end); + start = hidden.absoluteToVisibleColumn(start); + end = hidden.absoluteToVisibleColumn(end); if (start == end) { if (!hidden.isVisible(r[0])) @@ -547,40 +546,10 @@ public class AlignmentPanel extends GAlignmentPanel implements protected void validateAnnotationDimensions(boolean adjustPanelHeight) { int annotationHeight = getAnnotationPanel().adjustPanelHeight(); + annotationHeight = getAnnotationPanel() + .adjustForAlignFrame(adjustPanelHeight, annotationHeight); - if (adjustPanelHeight) - { - int rowHeight = av.getCharHeight(); - int alignmentHeight = rowHeight * av.getAlignment().getHeight(); - - /* - * Estimate available height in the AlignFrame for alignment + - * annotations. Deduct an estimate for title bar, menu bar, scale panel, - * hscroll, status bar (as these are not laid out we can't inspect their - * actual heights). Insets gives frame borders. - */ - int stuff = Platform.isAMac() ? 80 : 100; - Insets insets = alignFrame.getInsets(); - int availableHeight = alignFrame.getHeight() - stuff - insets.top - - insets.bottom; - - /* - * If not enough vertical space, maximize annotation height while keeping - * at least two rows of alignment visible - */ - if (annotationHeight + alignmentHeight > availableHeight) - { - annotationHeight = Math.min(annotationHeight, - availableHeight - 2 * rowHeight); - } - } - else - { - // maintain same window layout whilst updating sliders - annotationHeight = annotationScroller.getSize().height; - } hscroll.addNotify(); - annotationScroller.setPreferredSize( new Dimension(annotationScroller.getWidth(), annotationHeight)); @@ -619,6 +588,7 @@ public class AlignmentPanel extends GAlignmentPanel implements { annotationScroller.setVisible(true); annotationSpaceFillerHolder.setVisible(true); + validateAnnotationDimensions(false); } int canvasWidth = getSeqPanel().seqCanvas.getWidth(); @@ -678,7 +648,7 @@ public class AlignmentPanel extends GAlignmentPanel implements { // reset the width to exclude hidden columns width = av.getAlignment().getHiddenColumns() - .findColumnPosition(width); + .absoluteToVisibleColumn(width); } hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth(); @@ -874,15 +844,17 @@ public class AlignmentPanel extends GAlignmentPanel implements @Override public void paintComponent(Graphics g) { - invalidate(); + invalidate(); // needed so that the id width adjuster works correctly Dimension d = getIdPanel().getIdCanvas().getPreferredSize(); idPanelHolder.setPreferredSize(d); hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12)); - validate(); + + validate(); // needed so that the id width adjuster works correctly /* - * set scroll bar positions + * set scroll bar positions - tried to remove but necessary for split panel to resize correctly + * though I still think this call should be elsewhere. */ ViewportRanges ranges = av.getRanges(); setScrollValues(ranges.getStartRes(), ranges.getStartSeq()); @@ -944,30 +916,16 @@ public class AlignmentPanel extends GAlignmentPanel implements } /** - * DOCUMENT ME! - * - * @param pg - * DOCUMENT ME! - * @param pwidth - * DOCUMENT ME! - * @param pheight - * DOCUMENT ME! - * @param pi - * DOCUMENT ME! - * - * @return DOCUMENT ME! - * - * @throws PrinterException - * DOCUMENT ME! - */ - /** * Draws the alignment image, including sequence ids, sequences, and * annotation labels and annotations if shown, on either one or two Graphics - * context. + * contexts. * * @param pageWidth + * in pixels * @param pageHeight - * @param pi + * in pixels + * @param pageIndex + * (0, 1, ...) * @param idGraphics * the graphics context for sequence ids and annotation labels * @param alignmentGraphics @@ -976,7 +934,7 @@ public class AlignmentPanel extends GAlignmentPanel implements * @return * @throws PrinterException */ - public int printUnwrapped(int pageWidth, int pageHeight, int pi, + public int printUnwrapped(int pageWidth, int pageHeight, int pageIndex, Graphics idGraphics, Graphics alignmentGraphics) throws PrinterException { @@ -990,8 +948,8 @@ public class AlignmentPanel extends GAlignmentPanel implements : idWidth; FontMetrics fm = getFontMetrics(av.getFont()); - int charHeight = av.getCharHeight(); - int scaleHeight = charHeight + fm.getDescent(); + final int charHeight = av.getCharHeight(); + final int scaleHeight = charHeight + fm.getDescent(); idGraphics.setColor(Color.white); idGraphics.fillRect(0, 0, pageWidth, pageHeight); @@ -1000,29 +958,20 @@ public class AlignmentPanel extends GAlignmentPanel implements /* * How many sequences and residues can we fit on a printable page? */ - int totalRes = (pageWidth - idWidth) / av.getCharWidth(); - - int totalSeq = (pageHeight - scaleHeight) / charHeight - 1; - - int alignmentWidth = av.getAlignment().getWidth(); - int pagesWide = (alignmentWidth / totalRes) + 1; + final int totalRes = (pageWidth - idWidth) / av.getCharWidth(); - final int startRes = (pi % pagesWide) * totalRes; - int endRes = (startRes + totalRes) - 1; + final int totalSeq = (pageHeight - scaleHeight) / charHeight - 1; - if (endRes > (alignmentWidth - 1)) - { - endRes = alignmentWidth - 1; - } + final int alignmentWidth = av.getAlignment().getWidth(); + final int pagesWide = (alignmentWidth / totalRes) + 1; - final int startSeq = (pi / pagesWide) * totalSeq; - int endSeq = startSeq + totalSeq; + final int startRes = (pageIndex % pagesWide) * totalRes; + final int endRes = Math.min(startRes + totalRes - 1, + alignmentWidth - 1); - int alignmentHeight = av.getAlignment().getHeight(); - if (endSeq > alignmentHeight) - { - endSeq = alignmentHeight; - } + final int startSeq = (pageIndex / pagesWide) * totalSeq; + final int alignmentHeight = av.getAlignment().getHeight(); + final int endSeq = Math.min(startSeq + totalSeq, alignmentHeight); int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight; @@ -1033,7 +982,7 @@ public class AlignmentPanel extends GAlignmentPanel implements pagesHigh /= pageHeight; - if (pi >= (pagesWide * pagesHigh)) + if (pageIndex >= (pagesWide * pagesHigh)) { return Printable.NO_SUCH_PAGE; } @@ -1052,47 +1001,12 @@ public class AlignmentPanel extends GAlignmentPanel implements * then reset to top left (0, 0) */ idGraphics.translate(0, scaleHeight); - idGraphics.setFont(getIdPanel().getIdCanvas().getIdfont()); - Color currentColor = null; - Color currentTextColor = null; - - SequenceI seq; - for (int i = startSeq; i < endSeq; i++) - { - seq = av.getAlignment().getSequenceAt(i); - if ((av.getSelectionGroup() != null) - && av.getSelectionGroup().getSequences(null).contains(seq)) - { - /* - * gray out ids of sequences in selection group (if any) - */ - currentColor = Color.gray; - currentTextColor = Color.black; - } - else - { - currentColor = av.getSequenceColour(seq); - currentTextColor = Color.black; - } - - idGraphics.setColor(currentColor); - idGraphics.fillRect(0, (i - startSeq) * charHeight, idWidth, - charHeight); - - idGraphics.setColor(currentTextColor); + IdCanvas idCanvas = getIdPanel().getIdCanvas(); + List selection = av.getSelectionGroup() == null ? null + : av.getSelectionGroup().getSequences(null); + idCanvas.drawIds((Graphics2D) idGraphics, av, startSeq, endSeq - 1, + selection); - int xPos = 0; - String displayId = seq.getDisplayId(av.getShowJVSuffix()); - if (av.isRightAlignIds()) - { - fm = idGraphics.getFontMetrics(); - xPos = idWidth - fm.stringWidth(displayId) - 4; - } - - idGraphics.drawString(displayId, xPos, - (((i - startSeq) * charHeight) + charHeight) - - (charHeight / 5)); - } idGraphics.setFont(av.getFont()); idGraphics.translate(0, -scaleHeight); @@ -1102,7 +1016,7 @@ public class AlignmentPanel extends GAlignmentPanel implements */ alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight); getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes, - endRes, startSeq, endSeq); + endRes, startSeq, endSeq - 1); alignmentGraphics.translate(-alignmentGraphicsOffset, 0); if (av.isShowAnnotation() && (endSeq == alignmentHeight)) @@ -1132,31 +1046,27 @@ public class AlignmentPanel extends GAlignmentPanel implements } /** - * DOCUMENT ME! + * Prints one page of an alignment in wrapped mode. Returns + * Printable.PAGE_EXISTS (0) if a page was drawn, or Printable.NO_SUCH_PAGE if + * no page could be drawn (page number out of range). * - * @param pg - * DOCUMENT ME! - * @param pwidth - * DOCUMENT ME! - * @param pheight - * DOCUMENT ME! - * @param pi - * DOCUMENT ME! + * @param pageWidth + * @param pageHeight + * @param pageNumber + * (0, 1, ...) + * @param g * - * @return DOCUMENT ME! + * @return * * @throws PrinterException - * DOCUMENT ME! */ - public int printWrappedAlignment(int pwidth, int pheight, int pi, - Graphics pg) throws PrinterException + public int printWrappedAlignment(int pageWidth, int pageHeight, int pageNumber, + Graphics g) throws PrinterException { int annotationHeight = 0; - AnnotationLabels labels = null; if (av.isShowAnnotation()) { annotationHeight = getAnnotationPanel().adjustPanelHeight(); - labels = new AnnotationLabels(av); } int hgap = av.getCharHeight(); @@ -1174,68 +1084,43 @@ public class AlignmentPanel extends GAlignmentPanel implements if (av.hasHiddenColumns()) { maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth) - 1; + .absoluteToVisibleColumn(maxwidth) - 1; } int resWidth = getSeqPanel().seqCanvas - .getWrappedCanvasWidth(pwidth - idWidth); + .getWrappedCanvasWidth(pageWidth - idWidth); int totalHeight = cHeight * (maxwidth / resWidth + 1); - pg.setColor(Color.white); - pg.fillRect(0, 0, pwidth, pheight); - pg.setFont(av.getFont()); - - // ////////////// - // Draw the ids - pg.setColor(Color.black); + g.setColor(Color.white); + g.fillRect(0, 0, pageWidth, pageHeight); + g.setFont(av.getFont()); + g.setColor(Color.black); - pg.translate(0, -pi * pheight); - - pg.setClip(0, pi * pheight, pwidth, pheight); - - int ypos = hgap; - - do - { - for (int i = 0; i < av.getAlignment().getHeight(); i++) - { - pg.setFont(getIdPanel().getIdCanvas().getIdfont()); - SequenceI s = av.getAlignment().getSequenceAt(i); - String string = s.getDisplayId(av.getShowJVSuffix()); - int xPos = 0; - if (av.isRightAlignIds()) - { - FontMetrics fm = pg.getFontMetrics(); - xPos = idWidth - fm.stringWidth(string) - 4; - } - pg.drawString(string, xPos, - ((i * av.getCharHeight()) + ypos + av.getCharHeight()) - - (av.getCharHeight() / 5)); - } - if (labels != null) - { - pg.translate(-3, ypos - + (av.getAlignment().getHeight() * av.getCharHeight())); + /* + * method: print the whole wrapped alignment, but with a clip region that + * is restricted to the requested page; this supports selective print of + * single pages or ranges, (at the cost of some repeated processing in + * the 'normal' case, when all pages are printed) + */ + g.translate(0, -pageNumber * pageHeight); - pg.setFont(av.getFont()); - labels.drawComponent(pg, idWidth); - pg.translate(+3, -ypos - - (av.getAlignment().getHeight() * av.getCharHeight())); - } + g.setClip(0, pageNumber * pageHeight, pageWidth, pageHeight); - ypos += cHeight; - } while (ypos < totalHeight); + /* + * draw sequence ids and annotation labels (if shown) + */ + IdCanvas idCanvas = getIdPanel().getIdCanvas(); + idCanvas.drawIdsWrapped((Graphics2D) g, av, 0, totalHeight); - pg.translate(idWidth, 0); + g.translate(idWidth, 0); - getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(pg, pwidth - idWidth, + getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g, pageWidth - idWidth, totalHeight, 0); - if ((pi * pheight) < totalHeight) + if ((pageNumber * pageHeight) < totalHeight) { return Printable.PAGE_EXISTS; - } else { @@ -1366,7 +1251,7 @@ public class AlignmentPanel extends GAlignmentPanel implements if (av.hasHiddenColumns()) { maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth); + .absoluteToVisibleColumn(maxwidth); } int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight()) @@ -1584,7 +1469,7 @@ public class AlignmentPanel extends GAlignmentPanel implements if (av.hasHiddenColumns()) { maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth) - 1; + .absoluteToVisibleColumn(maxwidth) - 1; } int height = ((maxwidth / chunkWidth) + 1) * cHeight; @@ -1813,35 +1698,6 @@ public class AlignmentPanel extends GAlignmentPanel implements */ protected void scrollToCentre(SearchResultsI sr, int verticalOffset) { - /* - * To avoid jumpy vertical scrolling (if some sequences are gapped or not - * mapped), we can make the scroll-to location a sequence above the one - * actually mapped. - */ - SequenceI mappedTo = sr.getResults().get(0).getSequence(); - List seqs = av.getAlignment().getSequences(); - - /* - * This is like AlignmentI.findIndex(seq) but here we are matching the - * dataset sequence not the aligned sequence - */ - boolean matched = false; - for (SequenceI seq : seqs) - { - if (mappedTo == seq.getDatasetSequence()) - { - matched = true; - break; - } - } - if (!matched) - { - return; // failsafe, shouldn't happen - } - - /* - * Scroll to position but centring the target residue. - */ scrollToPosition(sr, verticalOffset, true, true); } diff --git a/src/jalview/gui/AnnotationColourChooser.java b/src/jalview/gui/AnnotationColourChooser.java index 153f70c..384635b 100644 --- a/src/jalview/gui/AnnotationColourChooser.java +++ b/src/jalview/gui/AnnotationColourChooser.java @@ -459,4 +459,11 @@ public class AnnotationColourChooser extends AnnotationRowFilter } } + @Override + protected void sliderDragReleased() + { + super.sliderDragReleased(); + ap.paintAlignment(true, true); + } + } diff --git a/src/jalview/gui/AnnotationExporter.java b/src/jalview/gui/AnnotationExporter.java index a619997..6fefbd0 100644 --- a/src/jalview/gui/AnnotationExporter.java +++ b/src/jalview/gui/AnnotationExporter.java @@ -21,8 +21,10 @@ package jalview.gui; import jalview.api.FeatureColourI; +import jalview.bin.Cache; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.io.AnnotationFile; import jalview.io.FeaturesFile; import jalview.io.JalviewFileChooser; @@ -34,6 +36,8 @@ import java.awt.Color; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.FileWriter; +import java.io.PrintWriter; import java.util.List; import java.util.Map; @@ -57,18 +61,22 @@ import javax.swing.SwingConstants; */ public class AnnotationExporter extends JPanel { - JInternalFrame frame; + private JInternalFrame frame; - AlignmentPanel ap; + private AlignmentPanel ap; - boolean features = true; + /* + * true if exporting features, false if exporting annotations + */ + private boolean exportFeatures = true; private AlignmentAnnotation[] annotations; private boolean wholeView; - public AnnotationExporter() + public AnnotationExporter(AlignmentPanel panel) { + this.ap = panel; try { jbInit(); @@ -84,47 +92,54 @@ public class AnnotationExporter extends JPanel frame.getPreferredSize().height); } - public void exportFeatures(AlignmentPanel ap) + /** + * Configures the diglog for options to export visible features + */ + public void exportFeatures() { - this.ap = ap; - features = true; + exportFeatures = true; CSVFormat.setVisible(false); frame.setTitle(MessageManager.getString("label.export_features")); } - public void exportAnnotations(AlignmentPanel ap) + /** + * Configures the dialog for options to export all visible annotations + */ + public void exportAnnotations() { - this.ap = ap; - annotations = ap.av.isShowAnnotation() ? null - : ap.av.getAlignment().getAlignmentAnnotation(); - wholeView = true; - startExportAnnotation(); + boolean showAnnotation = ap.av.isShowAnnotation(); + exportAnnotation(showAnnotation ? null + : ap.av.getAlignment().getAlignmentAnnotation(), true); } - public void exportAnnotations(AlignmentPanel alp, - AlignmentAnnotation[] toExport) + /** + * Configures the dialog for options to export the given annotation row + * + * @param toExport + */ + public void exportAnnotation(AlignmentAnnotation toExport) { - ap = alp; - annotations = toExport; - wholeView = false; - startExportAnnotation(); + exportAnnotation(new AlignmentAnnotation[] { toExport }, false); } - private void startExportAnnotation() + private void exportAnnotation(AlignmentAnnotation[] toExport, + boolean forWholeView) { - features = false; + wholeView = forWholeView; + annotations = toExport; + exportFeatures = false; GFFFormat.setVisible(false); CSVFormat.setVisible(true); frame.setTitle(MessageManager.getString("label.export_annotations")); } - public void toFile_actionPerformed(ActionEvent e) + private void toFile_actionPerformed() { JalviewFileChooser chooser = new JalviewFileChooser( - jalview.bin.Cache.getProperty("LAST_DIRECTORY")); + Cache.getProperty("LAST_DIRECTORY")); chooser.setFileView(new JalviewFileView()); - chooser.setDialogTitle(features + chooser.setDialogTitle(exportFeatures ? MessageManager.getString("label.save_features_to_file") : MessageManager.getString("label.save_annotation_to_file")); chooser.setToolTipText(MessageManager.getString("action.save")); @@ -133,13 +148,12 @@ public class AnnotationExporter extends JPanel if (value == JalviewFileChooser.APPROVE_OPTION) { - String text = getFileContents(); + String text = getText(); try { - java.io.PrintWriter out = new java.io.PrintWriter( - new java.io.FileWriter(chooser.getSelectedFile())); - + PrintWriter out = new PrintWriter( + new FileWriter(chooser.getSelectedFile())); out.print(text); out.close(); } catch (Exception ex) @@ -148,64 +162,89 @@ public class AnnotationExporter extends JPanel } } - close_actionPerformed(null); + close_actionPerformed(); + } + + /** + * Answers the text to output for either Features (in GFF or Jalview format) or + * Annotations (in CSV or Jalview format) + * + * @return + */ + private String getText() + { + return exportFeatures ? getFeaturesText() : getAnnotationsText(); } - private String getFileContents() + /** + * Returns the text contents for output of annotations in either CSV or Jalview + * format + * + * @return + */ + private String getAnnotationsText() { - String text = MessageManager - .getString("label.no_features_on_alignment"); - if (features) + String text; + if (CSVFormat.isSelected()) { - FeaturesFile formatter = new FeaturesFile(); - SequenceI[] sequences = ap.av.getAlignment().getSequencesArray(); - Map featureColours = ap.getFeatureRenderer() - .getDisplayedFeatureCols(); - List featureGroups = ap.getFeatureRenderer() - .getDisplayedFeatureGroups(); - boolean includeNonPositional = ap.av.isShowNPFeats(); - if (GFFFormat.isSelected()) - { - text = formatter.printGffFormat(sequences, featureColours, - featureGroups, includeNonPositional); - } - else - { - text = formatter.printJalviewFormat(sequences, featureColours, - featureGroups, includeNonPositional); - } + text = new AnnotationFile().printCSVAnnotations(annotations); } else { - if (CSVFormat.isSelected()) + if (wholeView) { - text = new AnnotationFile().printCSVAnnotations(annotations); + text = new AnnotationFile().printAnnotationsForView(ap.av); } else { - if (wholeView) - { - text = new AnnotationFile().printAnnotationsForView(ap.av); - } - else - { - text = new AnnotationFile().printAnnotations(annotations, null, - null); - } + text = new AnnotationFile().printAnnotations(annotations, null, + null); } } return text; } - public void toTextbox_actionPerformed(ActionEvent e) + /** + * Returns the text contents for output of features in either GFF or Jalview + * format + * + * @return + */ + private String getFeaturesText() + { + String text; + SequenceI[] sequences = ap.av.getAlignment().getSequencesArray(); + Map featureColours = ap.getFeatureRenderer() + .getDisplayedFeatureCols(); + Map featureFilters = ap.getFeatureRenderer() + .getFeatureFilters(); + List featureGroups = ap.getFeatureRenderer() + .getDisplayedFeatureGroups(); + boolean includeNonPositional = ap.av.isShowNPFeats(); + + FeaturesFile formatter = new FeaturesFile(); + if (GFFFormat.isSelected()) + { + text = formatter.printGffFormat(sequences, featureColours, + featureGroups, includeNonPositional); + } + else + { + text = formatter.printJalviewFormat(sequences, featureColours, + featureFilters, featureGroups, includeNonPositional); + } + return text; + } + + private void toTextbox_actionPerformed() { CutAndPasteTransfer cap = new CutAndPasteTransfer(); try { - String text = getFileContents(); + String text = getText(); cap.setText(text); - Desktop.addInternalFrame(cap, (features ? MessageManager + Desktop.addInternalFrame(cap, (exportFeatures ? MessageManager .formatMessage("label.features_for_params", new String[] { ap.alignFrame.getTitle() }) : MessageManager.formatMessage("label.annotations_for_params", @@ -214,7 +253,7 @@ public class AnnotationExporter extends JPanel 600, 500); } catch (OutOfMemoryError oom) { - new OOMWarning((features ? MessageManager.formatMessage( + new OOMWarning((exportFeatures ? MessageManager.formatMessage( "label.generating_features_for_params", new String[] { ap.alignFrame.getTitle() }) : MessageManager.formatMessage( @@ -225,10 +264,10 @@ public class AnnotationExporter extends JPanel cap.dispose(); } - close_actionPerformed(null); + close_actionPerformed(); } - public void close_actionPerformed(ActionEvent e) + private void close_actionPerformed() { try { @@ -248,7 +287,7 @@ public class AnnotationExporter extends JPanel @Override public void actionPerformed(ActionEvent e) { - toFile_actionPerformed(e); + toFile_actionPerformed(); } }); toTextbox.setText(MessageManager.getString("label.to_textbox")); @@ -257,7 +296,7 @@ public class AnnotationExporter extends JPanel @Override public void actionPerformed(ActionEvent e) { - toTextbox_actionPerformed(e); + toTextbox_actionPerformed(); } }); close.setText(MessageManager.getString("action.close")); @@ -266,7 +305,7 @@ public class AnnotationExporter extends JPanel @Override public void actionPerformed(ActionEvent e) { - close_actionPerformed(e); + close_actionPerformed(); } }); jalviewFormat.setOpaque(false); diff --git a/src/jalview/gui/AnnotationLabels.java b/src/jalview/gui/AnnotationLabels.java index b94a615..6f8b225 100755 --- a/src/jalview/gui/AnnotationLabels.java +++ b/src/jalview/gui/AnnotationLabels.java @@ -20,25 +20,28 @@ */ package jalview.gui; +import jalview.analysis.AlignSeq; import jalview.analysis.AlignmentUtils; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.Annotation; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.io.FileFormat; import jalview.io.FormatAdapter; +import jalview.util.Comparison; import jalview.util.MessageManager; +import jalview.util.Platform; import java.awt.Color; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.MediaTracker; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; @@ -52,6 +55,7 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.regex.Pattern; import javax.swing.JCheckBoxMenuItem; @@ -62,63 +66,74 @@ import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; /** - * DOCUMENT ME! - * - * @author $author$ - * @version $Revision$ + * The panel that holds the labels for alignment annotations, providing + * tooltips, context menus, drag to reorder rows, and drag to adjust panel + * height */ public class AnnotationLabels extends JPanel implements MouseListener, MouseMotionListener, ActionListener { + /** + * width in pixels within which height adjuster arrows are shown and active + */ + private static final int HEIGHT_ADJUSTER_WIDTH = 50; + + /** + * height in pixels for allowing height adjuster to be active + */ + private static int HEIGHT_ADJUSTER_HEIGHT = 10; + private static final Pattern LEFT_ANGLE_BRACKET_PATTERN = Pattern .compile("<"); - String TOGGLE_LABELSCALE = MessageManager + private static final Font font = new Font("Arial", Font.PLAIN, 11); + + private static final String TOGGLE_LABELSCALE = MessageManager .getString("label.scale_label_to_column"); - String ADDNEW = MessageManager.getString("label.add_new_row"); + private static final String ADDNEW = MessageManager + .getString("label.add_new_row"); - String EDITNAME = MessageManager + private static final String EDITNAME = MessageManager .getString("label.edit_label_description"); - String HIDE = MessageManager.getString("label.hide_row"); + private static final String HIDE = MessageManager + .getString("label.hide_row"); - String DELETE = MessageManager.getString("label.delete_row"); + private static final String DELETE = MessageManager + .getString("label.delete_row"); - String SHOWALL = MessageManager.getString("label.show_all_hidden_rows"); + private static final String SHOWALL = MessageManager + .getString("label.show_all_hidden_rows"); - String OUTPUT_TEXT = MessageManager.getString("label.export_annotation"); + private static final String OUTPUT_TEXT = MessageManager + .getString("label.export_annotation"); - String COPYCONS_SEQ = MessageManager + private static final String COPYCONS_SEQ = MessageManager .getString("label.copy_consensus_sequence"); - boolean resizePanel = false; - - Image image; + private final boolean debugRedraw = false; - AlignmentPanel ap; + private AlignmentPanel ap; AlignViewport av; - boolean resizing = false; - - MouseEvent dragEvent; + private MouseEvent dragEvent; - int oldY; + private int oldY; - int selectedRow; + private int selectedRow; private int scrollOffset = 0; - Font font = new Font("Arial", Font.PLAIN, 11); - private boolean hasHiddenRows; + private boolean resizePanel = false; + /** - * Creates a new AnnotationLabels object. + * Creates a new AnnotationLabels object * * @param ap - * DOCUMENT ME! */ public AnnotationLabels(AlignmentPanel ap) { @@ -126,30 +141,6 @@ public class AnnotationLabels extends JPanel av = ap.av; ToolTipManager.sharedInstance().registerComponent(this); - java.net.URL url = getClass().getResource("/images/idwidth.gif"); - Image temp = null; - - if (url != null) - { - temp = java.awt.Toolkit.getDefaultToolkit().createImage(url); - } - - try - { - MediaTracker mt = new MediaTracker(this); - mt.addImage(temp, 0); - mt.waitForID(0); - } catch (Exception ex) - { - } - - BufferedImage bi = new BufferedImage(temp.getHeight(this), - temp.getWidth(this), BufferedImage.TYPE_INT_RGB); - Graphics2D g = (Graphics2D) bi.getGraphics(); - g.rotate(Math.toRadians(90)); - g.drawImage(temp, 0, -bi.getWidth(this), this); - image = bi; - addMouseListener(this); addMouseMotionListener(this); addMouseWheelListener(ap.getAnnotationPanel()); @@ -266,9 +257,7 @@ public class AnnotationLabels extends JPanel } else if (evt.getActionCommand().equals(OUTPUT_TEXT)) { - new AnnotationExporter().exportAnnotations(ap, - new AlignmentAnnotation[] - { aa[selectedRow] }); + new AnnotationExporter(ap).exportAnnotation(aa[selectedRow]); } else if (evt.getActionCommand().equals(COPYCONS_SEQ)) { @@ -607,10 +596,9 @@ public class AnnotationLabels extends JPanel } /** - * DOCUMENT ME! + * Reorders annotation rows after a drag of a label * * @param evt - * DOCUMENT ME! */ @Override public void mouseReleased(MouseEvent evt) @@ -625,6 +613,9 @@ public class AnnotationLabels extends JPanel getSelectedRow(evt.getY() - getScrollOffset()); int end = selectedRow; + /* + * if dragging to resize instead, start == end + */ if (start != end) { // Swap these annotations @@ -648,31 +639,13 @@ public class AnnotationLabels extends JPanel } /** - * DOCUMENT ME! - * - * @param evt - * DOCUMENT ME! - */ - @Override - public void mouseEntered(MouseEvent evt) - { - if (evt.getY() < 10) - { - resizePanel = true; - repaint(); - } - } - - /** - * DOCUMENT ME! - * - * @param evt - * DOCUMENT ME! + * Removes the height adjuster image on leaving the panel, unless currently + * dragging it */ @Override public void mouseExited(MouseEvent evt) { - if (dragEvent == null) + if (resizePanel && dragEvent == null) { resizePanel = false; repaint(); @@ -680,10 +653,11 @@ public class AnnotationLabels extends JPanel } /** - * DOCUMENT ME! + * A mouse drag may be either an adjustment of the panel height (if flag + * resizePanel is set on), or a reordering of the annotation rows. The former + * is dealt with by this method, the latter in mouseReleased. * * @param evt - * DOCUMENT ME! */ @Override public void mouseDragged(MouseEvent evt) @@ -717,15 +691,14 @@ public class AnnotationLabels extends JPanel } /** - * DOCUMENT ME! + * Updates the tooltip as the mouse moves over the labels * * @param evt - * DOCUMENT ME! */ @Override public void mouseMoved(MouseEvent evt) { - resizePanel = evt.getY() < 10; + showOrHideAdjuster(evt); getSelectedRow(evt.getY() - getScrollOffset()); @@ -801,6 +774,26 @@ public class AnnotationLabels extends JPanel } } + /** + * Shows the height adjuster image if the mouse moves into the top left + * region, or hides it if the mouse leaves the regio + * + * @param evt + */ + protected void showOrHideAdjuster(MouseEvent evt) + { + boolean was = resizePanel; + resizePanel = evt.getY() < HEIGHT_ADJUSTER_HEIGHT && evt.getX() < HEIGHT_ADJUSTER_WIDTH; + + if (resizePanel != was) + { + setCursor(Cursor.getPredefinedCursor( + resizePanel ? Cursor.S_RESIZE_CURSOR + : Cursor.DEFAULT_CURSOR)); + repaint(); + } + } + @Override public void mouseClicked(MouseEvent evt) { @@ -820,11 +813,9 @@ public class AnnotationLabels extends JPanel // process modifiers SequenceGroup sg = ap.av.getSelectionGroup(); if (sg == null || sg == aa[selectedRow].groupRef - || !(jalview.util.Platform.isControlDown(evt) - || evt.isShiftDown())) + || !(Platform.isControlDown(evt) || evt.isShiftDown())) { - if (jalview.util.Platform.isControlDown(evt) - || evt.isShiftDown()) + if (Platform.isControlDown(evt) || evt.isShiftDown()) { // clone a new selection group from the associated group ap.av.setSelectionGroup( @@ -883,8 +874,7 @@ public class AnnotationLabels extends JPanel // we make a copy rather than edit the current selection if no // modifiers pressed // see Enhancement JAL-1557 - if (!(jalview.util.Platform.isControlDown(evt) - || evt.isShiftDown())) + if (!(Platform.isControlDown(evt) || evt.isShiftDown())) { sg = new SequenceGroup(sg); sg.clear(); @@ -892,7 +882,7 @@ public class AnnotationLabels extends JPanel } else { - if (jalview.util.Platform.isControlDown(evt)) + if (Platform.isControlDown(evt)) { sg.addOrRemove(aa[selectedRow].sequenceRef, true); } @@ -937,16 +927,17 @@ public class AnnotationLabels extends JPanel if (dseqs[0] == null) { dseqs[0] = new Sequence(sq); - dseqs[0].setSequence(jalview.analysis.AlignSeq.extractGaps( - jalview.util.Comparison.GapChars, sq.getSequenceAsString())); + dseqs[0].setSequence(AlignSeq.extractGaps(Comparison.GapChars, + sq.getSequenceAsString())); sq.setDatasetSequence(dseqs[0]); } Alignment ds = new Alignment(dseqs); if (av.hasHiddenColumns()) { - omitHidden = av.getAlignment().getHiddenColumns() - .getVisibleSequenceStrings(0, sq.getLength(), seqs); + Iterator it = av.getAlignment().getHiddenColumns() + .getVisContigsIterator(0, sq.getLength(), false); + omitHidden = new String[] { sq.getSequenceStringFromIterator(it) }; } int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 }; @@ -962,12 +953,12 @@ public class AnnotationLabels extends JPanel Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(new StringSelection(output), Desktop.instance); - ArrayList hiddenColumns = null; + HiddenColumns hiddenColumns = null; if (av.hasHiddenColumns()) { - hiddenColumns = av.getAlignment().getHiddenColumns() - .getHiddenColumnsCopy(); + hiddenColumns = new HiddenColumns( + av.getAlignment().getHiddenColumns()); } Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset @@ -1020,8 +1011,6 @@ public class AnnotationLabels extends JPanel drawComponent(g, false, width); } - private final boolean debugRedraw = false; - /** * Draw the full set of annotation Labels for the alignment at the given * cursor @@ -1204,11 +1193,7 @@ public class AnnotationLabels extends JPanel } } - if (resizePanel) - { - g.drawImage(image, 2, 0 - getScrollOffset(), this); - } - else if (dragEvent != null && aa != null) + if (!resizePanel && dragEvent != null && aa != null) { g.setColor(Color.lightGray); g.drawString(aa[selectedRow].label, dragEvent.getX(), @@ -1227,4 +1212,9 @@ public class AnnotationLabels extends JPanel { return scrollOffset; } + + @Override + public void mouseEntered(MouseEvent e) + { + } } diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index 438e81b..50971c7 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -30,6 +30,7 @@ import jalview.renderer.AwtRenderPanelI; import jalview.schemes.ResidueProperties; import jalview.util.Comparison; import jalview.util.MessageManager; +import jalview.util.Platform; import jalview.viewmodel.ViewportListenerI; import jalview.viewmodel.ViewportRanges; @@ -175,11 +176,12 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, if (e.isShiftDown()) { e.consume(); - if (e.getWheelRotation() > 0) + double wheelRotation = e.getPreciseWheelRotation(); + if (wheelRotation > 0) { av.getRanges().scrollRight(true); } - else + else if (wheelRotation < 0) { av.getRanges().scrollRight(false); } @@ -205,7 +207,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, @Override public Dimension getPreferredScrollableViewportSize() { - return getPreferredSize(); + Dimension ps = getPreferredSize(); + return new Dimension(ps.width, adjustForAlignFrame(false, ps.height)); } @Override @@ -724,7 +727,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, if (av.hasHiddenColumns()) { column = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(column); + .visibleToAbsoluteColumn(column); } AlignmentAnnotation ann = aa[row]; @@ -782,6 +785,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, { this.setToolTipText(JvSwingUtils.wrapTooltip(true, description)); } + else + { + this.setToolTipText(null); // no tooltip if null or empty description + } } else { @@ -904,6 +911,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, @Override public void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(Color.white); g.fillRect(0, 0, getWidth(), getHeight()); @@ -959,7 +968,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, gg.fillRect(0, 0, imgWidth, image.getHeight()); imageFresh = true; } - + drawComponent(gg, av.getRanges().getStartRes(), av.getRanges().getEndRes() + 1); imageFresh = false; @@ -992,10 +1001,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, int er = av.getRanges().getEndRes() + 1; int transX = 0; - long stime = System.currentTimeMillis(); gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.getCharWidth(), 0); - long mtime = System.currentTimeMillis(); if (horizontal > 0) // scrollbar pulled right, image to the left { @@ -1012,17 +1019,13 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, drawComponent(gg, sr, er); gg.translate(-transX, 0); - long dtime = System.currentTimeMillis(); + fastPaint = true; - repaint(); - long rtime = System.currentTimeMillis(); - if (debugRedraw) - { - System.err.println("Scroll:\t" + horizontal + "\tCopyArea:\t" - + (mtime - stime) + "\tDraw component:\t" + (dtime - mtime) - + "\tRepaint call:\t" + (rtime - dtime)); - } + // Call repaint on alignment panel so that repaints from other alignment + // panel components can be aggregated. Otherwise performance of the overview + // window and others may be adversely affected. + av.getAlignPanel().repaint(); } private volatile boolean lastImageGood = false; @@ -1195,4 +1198,49 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, repaint(); } } + + /** + * computes the visible height of the annotation panel + * + * @param adjustPanelHeight + * - when false, just adjust existing height according to other + * windows + * @param annotationHeight + * @return height to use for the ScrollerPreferredVisibleSize + */ + public int adjustForAlignFrame(boolean adjustPanelHeight, + int annotationHeight) + { + /* + * Estimate available height in the AlignFrame for alignment + + * annotations. Deduct an estimate for title bar, menu bar, scale panel, + * hscroll, status bar, insets. + */ + int stuff = (ap.getViewName() != null ? 30 : 0) + + (Platform.isAMac() ? 120 : 140); + int availableHeight = ap.alignFrame.getHeight() - stuff; + int rowHeight = av.getCharHeight(); + + if (adjustPanelHeight) + { + int alignmentHeight = rowHeight * av.getAlignment().getHeight(); + + /* + * If not enough vertical space, maximize annotation height while keeping + * at least two rows of alignment visible + */ + if (annotationHeight + alignmentHeight > availableHeight) + { + annotationHeight = Math.min(annotationHeight, + availableHeight - 2 * rowHeight); + } + } + else + { + // maintain same window layout whilst updating sliders + annotationHeight = Math.min(ap.annotationScroller.getSize().height, + availableHeight - 2 * rowHeight); + } + return annotationHeight; + } } diff --git a/src/jalview/gui/AnnotationRowFilter.java b/src/jalview/gui/AnnotationRowFilter.java index 71ad6a5..f13cb10 100644 --- a/src/jalview/gui/AnnotationRowFilter.java +++ b/src/jalview/gui/AnnotationRowFilter.java @@ -172,11 +172,7 @@ public abstract class AnnotationRowFilter extends JPanel @Override public void mouseReleased(MouseEvent evt) { - if (sliderDragging) - { - sliderDragging = false; - valueChanged(true); - } + sliderDragReleased(); } }); } @@ -523,4 +519,13 @@ public abstract class AnnotationRowFilter extends JPanel { this.annotations = anns; } + + protected void sliderDragReleased() + { + if (sliderDragging) + { + sliderDragging = false; + valueChanged(true); + } + } } diff --git a/src/jalview/gui/AppJmol.java b/src/jalview/gui/AppJmol.java index fef7451..6c934c8 100644 --- a/src/jalview/gui/AppJmol.java +++ b/src/jalview/gui/AppJmol.java @@ -44,7 +44,6 @@ import java.util.List; import java.util.Vector; import javax.swing.JCheckBoxMenuItem; -import javax.swing.JInternalFrame; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; @@ -58,7 +57,7 @@ public class AppJmol extends StructureViewerBase private static final String SPACE = " "; - private static final String BACKSLASH = "\""; + private static final String QUOTE = "\""; AppJmolBinding jmb; @@ -162,8 +161,9 @@ public class AppJmol extends StructureViewerBase { return progressBar; } + /** - * add a single PDB structure to a new or existing Jmol view + * display a single PDB structure in a new Jmol view * * @param pdbentry * @param seq @@ -174,33 +174,14 @@ public class AppJmol extends StructureViewerBase final AlignmentPanel ap) { progressBar = ap.alignFrame; - String pdbId = pdbentry.getId(); - /* - * If the PDB file is already loaded, the user may just choose to add to an - * existing viewer (or cancel) - */ - if (addAlreadyLoadedFile(seq, chains, ap, pdbId)) - { - return; - } - - /* - * Check if there are other Jmol views involving this alignment and prompt - * user about adding this molecule to one of them - */ - if (addToExistingViewer(pdbentry, seq, chains, ap, pdbId)) - { - return; - } - - /* - * If the options above are declined or do not apply, open a new viewer - */ - openNewJmol(ap, new PDBEntry[] { pdbentry }, new SequenceI[][] { seq }); + openNewJmol(ap, alignAddedStructures, new PDBEntry[] { pdbentry }, + new SequenceI[][] + { seq }); } - private void openNewJmol(AlignmentPanel ap, PDBEntry[] pdbentrys, + private void openNewJmol(AlignmentPanel ap, boolean alignAdded, + PDBEntry[] pdbentrys, SequenceI[][] seqs) { progressBar = ap.alignFrame; @@ -209,11 +190,9 @@ public class AppJmol extends StructureViewerBase addAlignmentPanel(ap); useAlignmentPanelForColourbyseq(ap); - if (pdbentrys.length > 1) - { - alignAddedStructures = true; - useAlignmentPanelForSuperposition(ap); - } + alignAddedStructures = alignAdded; + useAlignmentPanelForSuperposition(ap); + jmb.setColourBySequence(true); setSize(400, 400); // probably should be a configurable/dynamic default here initMenus(); @@ -234,41 +213,21 @@ public class AppJmol extends StructureViewerBase } /** - * create a new Jmol containing several structures superimposed using the - * given alignPanel. + * create a new Jmol containing several structures optionally superimposed + * using the given alignPanel. * * @param ap + * @param alignAdded + * - true to superimpose * @param pe * @param seqs */ - public AppJmol(AlignmentPanel ap, PDBEntry[] pe, SequenceI[][] seqs) + public AppJmol(AlignmentPanel ap, boolean alignAdded, PDBEntry[] pe, + SequenceI[][] seqs) { - openNewJmol(ap, pe, seqs); + openNewJmol(ap, alignAdded, pe, seqs); } - /** - * Returns a list of any Jmol viewers. The list is restricted to those linked - * to the given alignment panel if it is not null. - */ - @Override - protected List getViewersFor(AlignmentPanel apanel) - { - List result = new ArrayList<>(); - JInternalFrame[] frames = Desktop.instance.getAllFrames(); - - for (JInternalFrame frame : frames) - { - if (frame instanceof AppJmol) - { - if (apanel == null - || ((StructureViewerBase) frame).isLinkedWith(apanel)) - { - result.add((StructureViewerBase) frame); - } - } - } - return result; - } void initJmol(String command) { @@ -300,8 +259,6 @@ public class AppJmol extends StructureViewerBase jmb.setFinishedInit(true); } - boolean allChainsSelected = false; - @Override void showSelectedChains() { @@ -368,8 +325,8 @@ public class AppJmol extends StructureViewerBase StringBuilder fileList = new StringBuilder(); for (String s : files) { - fileList.append(SPACE).append(BACKSLASH) - .append(Platform.escapeString(s)).append(BACKSLASH); + fileList.append(SPACE).append(QUOTE) + .append(Platform.escapeString(s)).append(QUOTE); } String filesString = fileList.toString(); @@ -444,7 +401,7 @@ public class AppJmol extends StructureViewerBase jmb.updateColours(ap); } // do superposition if asked to - if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures) + if (alignAddedStructures) { alignAddedStructures(); } @@ -478,7 +435,7 @@ public class AppJmol extends StructureViewerBase } } }); - alignAddedStructures = false; + } /** @@ -507,6 +464,7 @@ public class AppJmol extends StructureViewerBase String file = jmb.getPdbEntry(pi).getFile(); if (file == null) { + // todo: extract block as method and pull up (also ChimeraViewFrame) // retrieve the pdb and store it locally AlignmentI pdbseq = null; pdbid = jmb.getPdbEntry(pi).getId(); diff --git a/src/jalview/gui/AppVarna.java b/src/jalview/gui/AppVarna.java index ea16f23..3a64716 100644 --- a/src/jalview/gui/AppVarna.java +++ b/src/jalview/gui/AppVarna.java @@ -120,6 +120,15 @@ public class AppVarna extends JInternalFrame } } + /** + * highlight a region from start to end (inclusive) on rna + * + * @param rna + * @param start + * - first base pair index (from 0) + * @param end + * - last base pair index (from 0) + */ public void highlightRegion(RNA rna, int start, int end) { clearLastSelection(); @@ -397,7 +406,8 @@ public class AppVarna extends JInternalFrame RnaModel rnaModel = models.get(rna); if (rnaModel.seq == sequence) { - int highlightPos = rnaModel.gapped ? index : position - 1; + int highlightPos = rnaModel.gapped ? index + : position - sequence.getStart(); mouseOverHighlighter.highlightRegion(rna, highlightPos, highlightPos); vab.updateSelectedRNA(rna); } @@ -418,15 +428,28 @@ public class AppVarna extends JInternalFrame { return; } - if (seqsel != null && seqsel.getSize() > 0) + + RnaModel rnaModel = models.get(rna); + + if (seqsel != null && seqsel.getSize() > 0 + && seqsel.contains(rnaModel.seq)) { int start = seqsel.getStartRes(), end = seqsel.getEndRes(); - ShiftList shift = offsets.get(rna); - if (shift != null) + if (rnaModel.gapped) { - start = shift.shift(start); - end = shift.shift(end); + ShiftList shift = offsets.get(rna); + if (shift != null) + { + start = shift.shift(start); + end = shift.shift(end); + } } + else + { + start = rnaModel.seq.findPosition(start) - rnaModel.seq.getStart(); + end = rnaModel.seq.findPosition(end) - rnaModel.seq.getStart(); + } + selectionHighlighter.highlightRegion(rna, start, end); selectionHighlighter.getLastHighlight() .setOutlineColor(seqsel.getOutlineColour()); diff --git a/src/jalview/gui/AquaInternalFrameManager.java b/src/jalview/gui/AquaInternalFrameManager.java index ea809eb..8ef204c 100644 --- a/src/jalview/gui/AquaInternalFrameManager.java +++ b/src/jalview/gui/AquaInternalFrameManager.java @@ -60,7 +60,7 @@ import javax.swing.JInternalFrame; * around to the bottom of the window stack (as the original implementation * does) * - * @see com.sun.java.swing.plaf.windows.WindowsDesktopManager + * see com.sun.java.swing.plaf.windows.WindowsDesktopManager */ public class AquaInternalFrameManager extends DefaultDesktopManager { @@ -152,11 +152,12 @@ public class AquaInternalFrameManager extends DefaultDesktopManager super.activateFrame(f); } - // If this is the first activation, add to child list. - if (fChildFrames.indexOf(f) == -1) + // add or relocate to top of stack + if (fChildFrames.indexOf(f) != -1) { - fChildFrames.addElement(f); + fChildFrames.remove(f); } + fChildFrames.addElement(f); if (fCurrentFrame != null && f != fCurrentFrame) { @@ -254,4 +255,4 @@ public class AquaInternalFrameManager extends DefaultDesktopManager { switchFrame(false); } -} \ No newline at end of file +} diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index e403dba..f674c7e 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -169,8 +169,8 @@ public class CalculationChooser extends JPanel JPanel treePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); treePanel.setOpaque(false); - treePanel.setBorder(BorderFactory - .createTitledBorder(MessageManager.getString("label.tree"))); + JvSwingUtils.createTitledBorder(treePanel, + MessageManager.getString("label.tree"), true); // then copy the inset dimensions for the border-less PCA panel JPanel pcaBorderless = new JPanel(new FlowLayout(FlowLayout.LEFT)); diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java index 89de2e8..d07a7c2 100644 --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@ -100,41 +100,34 @@ public class ChimeraViewFrame extends StructureViewerBase savemenu.setVisible(false); // not yet implemented viewMenu.add(fitToWindow); - /* - * exchange of Jalview features and Chimera attributes is for now - * an optionally enabled experimental feature - */ - if (Desktop.instance.showExperimental()) + JMenuItem writeFeatures = new JMenuItem( + MessageManager.getString("label.create_chimera_attributes")); + writeFeatures.setToolTipText(MessageManager + .getString("label.create_chimera_attributes_tip")); + writeFeatures.addActionListener(new ActionListener() { - JMenuItem writeFeatures = new JMenuItem( - MessageManager.getString("label.create_chimera_attributes")); - writeFeatures.setToolTipText(MessageManager - .getString("label.create_chimera_attributes_tip")); - writeFeatures.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - sendFeaturesToChimera(); - } - }); - viewerActionMenu.add(writeFeatures); - - final JMenu fetchAttributes = new JMenu( - MessageManager.getString("label.fetch_chimera_attributes")); - fetchAttributes.setToolTipText(MessageManager - .getString("label.fetch_chimera_attributes_tip")); - fetchAttributes.addMouseListener(new MouseAdapter() + @Override + public void actionPerformed(ActionEvent e) { + sendFeaturesToChimera(); + } + }); + viewerActionMenu.add(writeFeatures); - @Override - public void mouseEntered(MouseEvent e) - { - buildAttributesMenu(fetchAttributes); - } - }); - viewerActionMenu.add(fetchAttributes); - } + final JMenu fetchAttributes = new JMenu( + MessageManager.getString("label.fetch_chimera_attributes")); + fetchAttributes.setToolTipText( + MessageManager.getString("label.fetch_chimera_attributes_tip")); + fetchAttributes.addMouseListener(new MouseAdapter() + { + + @Override + public void mouseEntered(MouseEvent e) + { + buildAttributesMenu(fetchAttributes); + } + }); + viewerActionMenu.add(fetchAttributes); } /** @@ -202,7 +195,7 @@ public class ChimeraViewFrame extends StructureViewerBase } /** - * add a single PDB structure to a new or existing Chimera view + * open a single PDB structure in a new Chimera view * * @param pdbentry * @param seq @@ -213,30 +206,7 @@ public class ChimeraViewFrame extends StructureViewerBase String[] chains, final AlignmentPanel ap) { this(); - String pdbId = pdbentry.getId(); - - /* - * If the PDB file is already loaded, the user may just choose to add to an - * existing viewer (or cancel) - */ - if (addAlreadyLoadedFile(seq, chains, ap, pdbId)) - { - return; - } - /* - * Check if there are other Chimera views involving this alignment and give - * user the option to add and align this molecule to one of them (or cancel) - */ - if (addToExistingViewer(pdbentry, seq, chains, ap, pdbId)) - { - return; - } - - /* - * If the options above are declined or do not apply, show the structure in - * a new viewer - */ openNewChimera(ap, new PDBEntry[] { pdbentry }, new SequenceI[][] { seq }); @@ -264,7 +234,6 @@ public class ChimeraViewFrame extends StructureViewerBase if (pdbentrys.length > 1) { - alignAddedStructures = true; useAlignmentPanelForSuperposition(ap); } jmb.setColourBySequence(true); @@ -323,17 +292,19 @@ public class ChimeraViewFrame extends StructureViewerBase } /** - * create a new viewer containing several structures superimposed using the - * given alignPanel. + * create a new viewer containing several structures, optionally superimposed + * using the given alignPanel. * * @param pe * @param seqs * @param ap */ - public ChimeraViewFrame(PDBEntry[] pe, SequenceI[][] seqs, + public ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded, + SequenceI[][] seqs, AlignmentPanel ap) { this(); + setAlignAddedStructures(alignAdded); openNewChimera(ap, pe, seqs); } @@ -352,29 +323,6 @@ public class ChimeraViewFrame extends StructureViewerBase } /** - * Returns a list of any Chimera viewers in the desktop. The list is - * restricted to those linked to the given alignment panel if it is not null. - */ - @Override - protected List getViewersFor(AlignmentPanel ap) - { - List result = new ArrayList<>(); - JInternalFrame[] frames = Desktop.instance.getAllFrames(); - - for (JInternalFrame frame : frames) - { - if (frame instanceof ChimeraViewFrame) - { - if (ap == null || ((StructureViewerBase) frame).isLinkedWith(ap)) - { - result.add((StructureViewerBase) frame); - } - } - } - return result; - } - - /** * Launch Chimera. If we have a chimera session file name, send Chimera the * command to open its saved session file. */ @@ -641,7 +589,7 @@ public class ChimeraViewFrame extends StructureViewerBase jmb.updateColours(ap); } // do superposition if asked to - if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures) + if (alignAddedStructures) { new Thread(new Runnable() { @@ -651,7 +599,6 @@ public class ChimeraViewFrame extends StructureViewerBase alignStructs_withAllAlignPanels(); } }).start(); - alignAddedStructures = false; } addingStructures = false; } @@ -681,7 +628,6 @@ public class ChimeraViewFrame extends StructureViewerBase private String fetchPdbFile(PDBEntry processingEntry) throws Exception { - // FIXME: this is duplicated code with Jmol frame ? String filePath = null; Pdb pdbclient = new Pdb(); AlignmentI pdbseq = null; diff --git a/src/jalview/gui/CrossRefAction.java b/src/jalview/gui/CrossRefAction.java index 2d1dfd4..85f2498 100644 --- a/src/jalview/gui/CrossRefAction.java +++ b/src/jalview/gui/CrossRefAction.java @@ -27,17 +27,25 @@ import jalview.api.FeatureSettingsModelI; import jalview.bin.Cache; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; +import jalview.datamodel.DBRefEntry; import jalview.datamodel.DBRefSource; +import jalview.datamodel.GeneLociI; import jalview.datamodel.SequenceI; +import jalview.ext.ensembl.EnsemblInfo; +import jalview.ext.ensembl.EnsemblMap; import jalview.io.gff.SequenceOntologyI; import jalview.structure.StructureSelectionManager; +import jalview.util.DBRefUtils; +import jalview.util.MapList; +import jalview.util.MappingUtils; import jalview.util.MessageManager; import jalview.ws.SequenceFetcher; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; - -import javax.swing.JOptionPane; +import java.util.Map; +import java.util.Set; /** * Factory constructor and runnable for discovering and displaying @@ -52,13 +60,13 @@ public class CrossRefAction implements Runnable private SequenceI[] sel; - private boolean _odna; + private final boolean _odna; private String source; - List xrefViews = new ArrayList(); + List xrefViews = new ArrayList<>(); - public List getXrefViews() + List getXrefViews() { return xrefViews; } @@ -90,6 +98,13 @@ public class CrossRefAction implements Runnable { return; } + + /* + * try to look up chromosomal coordinates for nucleotide + * sequences (if not already retrieved) + */ + findGeneLoci(xrefs.getSequences()); + /* * get display scheme (if any) to apply to features */ @@ -113,75 +128,14 @@ public class CrossRefAction implements Runnable if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true)) { - boolean copyAlignmentIsAligned = false; - if (dna) - { - copyAlignment = AlignmentUtils.makeCdsAlignment(sel, dataset, - xrefsAlignment.getSequencesArray()); - if (copyAlignment.getHeight() == 0) - { - JvOptionPane.showMessageDialog(alignFrame, - MessageManager.getString("label.cant_map_cds"), - MessageManager.getString("label.operation_failed"), - JvOptionPane.OK_OPTION); - System.err.println("Failed to make CDS alignment"); - } - - /* - * pending getting Embl transcripts to 'align', - * we are only doing this for Ensembl - */ - // TODO proper criteria for 'can align as cdna' - if (DBRefSource.ENSEMBL.equalsIgnoreCase(source) - || AlignmentUtils.looksLikeEnsembl(alignment)) - { - copyAlignment.alignAs(alignment); - copyAlignmentIsAligned = true; - } - } - else + copyAlignment = copyAlignmentForSplitFrame(alignment, dataset, dna, + xrefs, xrefsAlignment); + if (copyAlignment == null) { - copyAlignment = AlignmentUtils.makeCopyAlignment(sel, - xrefs.getSequencesArray(), dataset); - } - copyAlignment - .setGapCharacter(alignFrame.viewport.getGapCharacter()); - - StructureSelectionManager ssm = StructureSelectionManager - .getStructureSelectionManager(Desktop.instance); - - /* - * register any new mappings for sequence mouseover etc - * (will not duplicate any previously registered mappings) - */ - ssm.registerMappings(dataset.getCodonFrames()); - - if (copyAlignment.getHeight() <= 0) - { - System.err.println( - "No Sequences generated for xRef type " + source); - return; - } - /* - * align protein to dna - */ - if (dna && copyAlignmentIsAligned) - { - xrefsAlignment.alignAs(copyAlignment); - } - else - { - /* - * align cdna to protein - currently only if - * fetching and aligning Ensembl transcripts! - */ - // TODO: generalise for other sources of locus/transcript/cds data - if (dna && DBRefSource.ENSEMBL.equalsIgnoreCase(source)) - { - copyAlignment.alignAs(xrefsAlignment); - } + return; // failed } } + /* * build AlignFrame(s) according to available alignment data */ @@ -207,6 +161,7 @@ public class CrossRefAction implements Runnable xrefViews.add(newFrame.alignPanel); return; // via finally clause } + AlignFrame copyThis = new AlignFrame(copyAlignment, AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); copyThis.setTitle(alignFrame.getTitle()); @@ -221,10 +176,14 @@ public class CrossRefAction implements Runnable /* * copy feature rendering settings to split frame */ - newFrame.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer() - .transferSettings(myFeatureStyling); - copyThis.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer() - .transferSettings(myFeatureStyling); + FeatureRenderer fr1 = newFrame.alignPanel.getSeqPanel().seqCanvas + .getFeatureRenderer(); + fr1.transferSettings(myFeatureStyling); + fr1.findAllFeatures(true); + FeatureRenderer fr2 = copyThis.alignPanel.getSeqPanel().seqCanvas + .getFeatureRenderer(); + fr2.transferSettings(myFeatureStyling); + fr2.findAllFeatures(true); /* * apply 'database source' feature configuration @@ -263,6 +222,260 @@ public class CrossRefAction implements Runnable } /** + * Tries to add chromosomal coordinates to any nucleotide sequence which does + * not already have them. Coordinates are retrieved from Ensembl given an + * Ensembl identifier, either on the sequence itself or on a peptide sequence + * it has a reference to. + * + *

          +   * Example (human):
          +   * - fetch EMBLCDS cross-references for Uniprot entry P30419
          +   * - the EMBL sequences do not have xrefs to Ensembl
          +   * - the Uniprot entry has xrefs to 
          +   *    ENSP00000258960, ENSP00000468424, ENST00000258960, ENST00000592782
          +   * - either of the transcript ids can be used to retrieve gene loci e.g.
          +   *    http://rest.ensembl.org/map/cds/ENST00000592782/1..100000
          +   * Example (invertebrate):
          +   * - fetch EMBLCDS cross-references for Uniprot entry Q43517 (FER1_SOLLC)
          +   * - the Uniprot entry has an xref to ENSEMBLPLANTS Solyc10g044520.1.1
          +   * - can retrieve gene loci with
          +   *    http://rest.ensemblgenomes.org/map/cds/Solyc10g044520.1.1/1..100000
          +   * 
          + * + * @param sequences + */ + public static void findGeneLoci(List sequences) + { + Map retrievedLoci = new HashMap<>(); + for (SequenceI seq : sequences) + { + findGeneLoci(seq, retrievedLoci); + } + } + + /** + * Tres to find chromosomal coordinates for the sequence, by searching its + * direct and indirect cross-references for Ensembl. If the loci have already + * been retrieved, just reads them out of the map of retrievedLoci; this is + * the case of an alternative transcript for the same protein. Otherwise calls + * a REST service to retrieve the loci, and if successful, adds them to the + * sequence and to the retrievedLoci. + * + * @param seq + * @param retrievedLoci + */ + static void findGeneLoci(SequenceI seq, + Map retrievedLoci) + { + /* + * don't replace any existing chromosomal coordinates + */ + if (seq == null || seq.isProtein() || seq.getGeneLoci() != null + || seq.getDBRefs() == null) + { + return; + } + + Set ensemblDivisions = new EnsemblInfo().getDivisions(); + + /* + * first look for direct dbrefs from sequence to Ensembl + */ + String[] divisionsArray = ensemblDivisions + .toArray(new String[ensemblDivisions.size()]); + DBRefEntry[] seqRefs = seq.getDBRefs(); + DBRefEntry[] directEnsemblRefs = DBRefUtils.selectRefs(seqRefs, + divisionsArray); + if (directEnsemblRefs != null) + { + for (DBRefEntry ensemblRef : directEnsemblRefs) + { + if (fetchGeneLoci(seq, ensemblRef, retrievedLoci)) + { + return; + } + } + } + + /* + * else look for indirect dbrefs from sequence to Ensembl + */ + for (DBRefEntry dbref : seq.getDBRefs()) + { + if (dbref.getMap() != null && dbref.getMap().getTo() != null) + { + DBRefEntry[] dbrefs = dbref.getMap().getTo().getDBRefs(); + DBRefEntry[] indirectEnsemblRefs = DBRefUtils.selectRefs(dbrefs, + divisionsArray); + if (indirectEnsemblRefs != null) + { + for (DBRefEntry ensemblRef : indirectEnsemblRefs) + { + if (fetchGeneLoci(seq, ensemblRef, retrievedLoci)) + { + return; + } + } + } + } + } + } + + /** + * Retrieves chromosomal coordinates for the Ensembl (or EnsemblGenomes) + * identifier in dbref. If successful, and the sequence length matches gene + * loci length, then add it to the sequence, and to the retrievedLoci map. + * Answers true if successful, else false. + * + * @param seq + * @param dbref + * @param retrievedLoci + * @return + */ + static boolean fetchGeneLoci(SequenceI seq, DBRefEntry dbref, + Map retrievedLoci) + { + String accession = dbref.getAccessionId(); + String division = dbref.getSource(); + + /* + * hack: ignore cross-references to Ensembl protein ids + * (or use map/translation perhaps?) + * todo: is there an equivalent in EnsemblGenomes? + */ + if (accession.startsWith("ENSP")) + { + return false; + } + EnsemblMap mapper = new EnsemblMap(); + + /* + * try CDS mapping first + */ + GeneLociI geneLoci = mapper.getCdsMapping(division, accession, 1, + seq.getLength()); + if (geneLoci != null) + { + MapList map = geneLoci.getMap(); + int mappedFromLength = MappingUtils.getLength(map.getFromRanges()); + if (mappedFromLength == seq.getLength()) + { + seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(), + geneLoci.getChromosomeId(), geneLoci.getMap()); + retrievedLoci.put(dbref, geneLoci); + return true; + } + } + + /* + * else try CDNA mapping + */ + geneLoci = mapper.getCdnaMapping(division, accession, 1, + seq.getLength()); + if (geneLoci != null) + { + MapList map = geneLoci.getMap(); + int mappedFromLength = MappingUtils.getLength(map.getFromRanges()); + if (mappedFromLength == seq.getLength()) + { + seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(), + geneLoci.getChromosomeId(), geneLoci.getMap()); + retrievedLoci.put(dbref, geneLoci); + return true; + } + } + + return false; + } + + /** + * @param alignment + * @param dataset + * @param dna + * @param xrefs + * @param xrefsAlignment + * @return + */ + protected AlignmentI copyAlignmentForSplitFrame(AlignmentI alignment, + AlignmentI dataset, boolean dna, AlignmentI xrefs, + AlignmentI xrefsAlignment) + { + AlignmentI copyAlignment; + boolean copyAlignmentIsAligned = false; + if (dna) + { + copyAlignment = AlignmentUtils.makeCdsAlignment(sel, dataset, + xrefsAlignment.getSequencesArray()); + if (copyAlignment.getHeight() == 0) + { + JvOptionPane.showMessageDialog(alignFrame, + MessageManager.getString("label.cant_map_cds"), + MessageManager.getString("label.operation_failed"), + JvOptionPane.OK_OPTION); + System.err.println("Failed to make CDS alignment"); + return null; + } + + /* + * pending getting Embl transcripts to 'align', + * we are only doing this for Ensembl + */ + // TODO proper criteria for 'can align as cdna' + if (DBRefSource.ENSEMBL.equalsIgnoreCase(source) + || AlignmentUtils.looksLikeEnsembl(alignment)) + { + copyAlignment.alignAs(alignment); + copyAlignmentIsAligned = true; + } + } + else + { + copyAlignment = AlignmentUtils.makeCopyAlignment(sel, + xrefs.getSequencesArray(), dataset); + } + copyAlignment + .setGapCharacter(alignFrame.viewport.getGapCharacter()); + + StructureSelectionManager ssm = StructureSelectionManager + .getStructureSelectionManager(Desktop.instance); + + /* + * register any new mappings for sequence mouseover etc + * (will not duplicate any previously registered mappings) + */ + ssm.registerMappings(dataset.getCodonFrames()); + + if (copyAlignment.getHeight() <= 0) + { + System.err.println( + "No Sequences generated for xRef type " + source); + return null; + } + + /* + * align protein to dna + */ + if (dna && copyAlignmentIsAligned) + { + xrefsAlignment.alignAs(copyAlignment); + } + else + { + /* + * align cdna to protein - currently only if + * fetching and aligning Ensembl transcripts! + */ + // TODO: generalise for other sources of locus/transcript/cds data + if (dna && DBRefSource.ENSEMBL.equalsIgnoreCase(source)) + { + copyAlignment.alignAs(xrefsAlignment); + } + } + + return copyAlignment; + } + + /** * Makes an alignment containing the given sequences, and adds them to the * given dataset, which is also set as the dataset for the new alignment * @@ -291,20 +504,28 @@ public class CrossRefAction implements Runnable return al; } - public CrossRefAction(AlignFrame alignFrame, SequenceI[] sel, - boolean _odna, String source) + /** + * Constructor + * + * @param af + * @param seqs + * @param fromDna + * @param dbSource + */ + CrossRefAction(AlignFrame af, SequenceI[] seqs, boolean fromDna, + String dbSource) { - this.alignFrame = alignFrame; - this.sel = sel; - this._odna = _odna; - this.source = source; + this.alignFrame = af; + this.sel = seqs; + this._odna = fromDna; + this.source = dbSource; } - public static CrossRefAction showProductsFor(final SequenceI[] sel, - final boolean _odna, final String source, + public static CrossRefAction getHandlerFor(final SequenceI[] sel, + final boolean fromDna, final String source, final AlignFrame alignFrame) { - return new CrossRefAction(alignFrame, sel, _odna, source); + return new CrossRefAction(alignFrame, sel, fromDna, source); } } diff --git a/src/jalview/gui/CutAndPasteHtmlTransfer.java b/src/jalview/gui/CutAndPasteHtmlTransfer.java index 71a1520..2e51bce 100644 --- a/src/jalview/gui/CutAndPasteHtmlTransfer.java +++ b/src/jalview/gui/CutAndPasteHtmlTransfer.java @@ -141,6 +141,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer */ public void setText(String text) { + textarea.setDocument(textarea.getEditorKit().createDefaultDocument()); textarea.setText(text); } diff --git a/src/jalview/gui/DasSourceBrowser.java b/src/jalview/gui/DasSourceBrowser.java deleted file mode 100644 index 8570ac3..0000000 --- a/src/jalview/gui/DasSourceBrowser.java +++ /dev/null @@ -1,864 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.gui; - -import jalview.jbgui.GDasSourceBrowser; -import jalview.util.MessageManager; -import jalview.util.TableSorter; -import jalview.ws.dbsources.das.api.DasSourceRegistryI; -import jalview.ws.dbsources.das.api.jalviewSourceI; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.StringTokenizer; -import java.util.Vector; - -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.ListSelectionModel; -import javax.swing.SwingUtilities; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.AbstractTableModel; - -import org.biodas.jdas.schema.sources.CAPABILITY; -import org.biodas.jdas.schema.sources.COORDINATES; -import org.biodas.jdas.schema.sources.PROP; -import org.biodas.jdas.schema.sources.VERSION; - -public class DasSourceBrowser extends GDasSourceBrowser - implements Runnable, ListSelectionListener -{ - DasSourceRegistryI sourceRegistry = null; - - Vector selectedSources; - - public DasSourceBrowser(FeatureSettings featureSettings) - { - fs = featureSettings; - // TODO DasSourceRegistryProvider API - sourceRegistry = jalview.bin.Cache.getDasSourceRegistry(); - String registry = sourceRegistry.getDasRegistryURL(); - - registryURL.setText(registry); - - setSelectedFromProperties(); - - displayFullDetails(null); - table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - filter1.addListSelectionListener(this); - filter2.addListSelectionListener(this); - filter3.addListSelectionListener(this); - - // Ask to be notified of selection changes. - ListSelectionModel rowSM = table.getSelectionModel(); - rowSM.addListSelectionListener(new ListSelectionListener() - { - @Override - public void valueChanged(ListSelectionEvent e) - { - ListSelectionModel lsm = (ListSelectionModel) e.getSource(); - if (!lsm.isSelectionEmpty()) - { - int selectedRow = lsm.getMinSelectionIndex(); - displayFullDetails(table.getValueAt(selectedRow, 0).toString()); - } - } - }); - - table.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent evt) - { - if (evt.getClickCount() == 2 || evt.isPopupTrigger()) - { - editRemoveLocalSource(evt); - } - } - }); - - if (sourceRegistry.getSources() != null) - { - init(); - } - } - - FeatureSettings fs = null; - - private boolean loadingDasSources; - - public DasSourceBrowser() - { - this(null); - } - - @Override - public void paintComponent(java.awt.Graphics g) - { - if (sourceRegistry == null) - { - Thread worker = new Thread(this); - worker.start(); - } - } - - void init() - { - List sources = sourceRegistry.getSources(); - int dSize = sources.size(); - Object[][] data = new Object[dSize][2]; - for (int i = 0; i < dSize; i++) - { - data[i][0] = sources.get(i).getTitle(); // what's equivalent of nickname - data[i][1] = new Boolean( - selectedSources.contains(sources.get(i).getTitle())); - } - - refreshTableData(data); - setCapabilities(sourceRegistry); - - javax.swing.SwingUtilities.invokeLater(new Runnable() - { - @Override - public void run() - { - TableSorter sorter = (TableSorter) table.getModel(); - sorter.setSortingStatus(1, TableSorter.DESCENDING); - sorter.setSortingStatus(1, TableSorter.NOT_SORTED); - } - }); - - progressBar.setIndeterminate(false); - progressBar.setVisible(false); - addLocal.setVisible(true); - refresh.setVisible(true); - } - - public void refreshTableData(Object[][] data) - { - TableSorter sorter = new TableSorter(new DASTableModel(data)); - sorter.setTableHeader(table.getTableHeader()); - table.setModel(sorter); - } - - void displayFullDetails(String nickName) - { - - StringBuffer text = new StringBuffer( - ""); - - if (nickName == null) - { - fullDetails.setText(text + MessageManager - .getString("label.select_das_service_from_table")); - return; - } - - int dSize = sourceRegistry.getSources().size(); - for (jalviewSourceI ds : sourceRegistry.getSources()) - { - if (!ds.getTitle().equals(nickName)) - { - continue; - } - - VERSION latest = ds.getVersion(); - text.append( - "Id: " + ds.getUri() + "
          "); - text.append("Nickname: " - + ds.getTitle() + "
          "); - - text.append("URL: " + ds.getSourceURL() + "" - + "
          "); - if (!ds.isLocal()) - { - if (ds.getDocHref() != null && ds.getDocHref().length() > 0) - { - text.append("Site: " + ds.getDocHref() + "" - + "
          "); - } - - text.append("Description: " - + ds.getDescription() + "
          "); - - text.append( - "Admin Email: " + ds.getEmail() + "" - + "
          "); - - text.append("Registered at: " - + latest.getCreated() + "
          "); - - // TODO: Identify last successful test date - // text.append("Last successful test: " - // + latest.dasSources[i].getLeaseDate() + "
          "); - } - else - { - text.append("Source was added manually.
          "); - } - text.append("Labels: "); - boolean b = false; - for (PROP labl : latest.getPROP()) - { - if (labl.getName().equalsIgnoreCase("LABEL")) - { - if (b) - { - text.append(","); - } - text.append(" "); - - text.append(labl.getValue()); - b = true; - } - ; - } - text.append("
          "); - - text.append("Capabilities: "); - CAPABILITY[] scap = latest.getCAPABILITY().toArray(new CAPABILITY[0]); - for (int j = 0; j < scap.length; j++) - { - text.append(scap[j].getType()); - if (j < scap.length - 1) - { - text.append(", "); - } - } - text.append("
          "); - - text.append("Coordinates:"); - int i = 1; - for (COORDINATES dcs : latest.getCOORDINATES()) - { - text.append("
          " + i++ + ". "); - text.append(dcs.getAuthority() + " : " + dcs.getSource()); - if (dcs.getTaxid() != null && dcs.getTaxid().trim().length() > 0) - { - text.append(" [TaxId:" + dcs.getTaxid() + "]"); - } - if (dcs.getVersion() != null - && dcs.getVersion().trim().length() > 0) - { - { - text.append(" {v. " + dcs.getVersion() + "}"); - } - } - text.append(" (" + dcs.getUri() - + ")"); - } - text.append("
          "); - - break; - } - - fullDetails.setText(text.toString()); - javax.swing.SwingUtilities.invokeLater(new Runnable() - { - @Override - public void run() - { - fullDetailsScrollpane.getVerticalScrollBar().setValue(0); - } - }); - } - - @Override - public void run() - { - loadingDasSources = true; - - addLocal.setVisible(false); - refresh.setVisible(false); - progressBar.setVisible(true); - progressBar.setIndeterminate(true); - setParentGuiEnabled(false); - // Refresh the source list. - sourceRegistry.refreshSources(); - - init(); - - setParentGuiEnabled(true); - loadingDasSources = false; - - } - - private void setParentGuiEnabled(boolean b) - { - if (fs != null) - { - fs.fetchDAS.setEnabled(b); - fs.saveDAS.setEnabled(b); - } - } - - public Vector getSelectedSources() - { - // wait around if we're still loading. - while (sourceRegistry == null) - { - if (!loadingDasSources) - { - new Thread(this).start(); - try - { - Thread.sleep(5); - } catch (Exception e) - { - } - ; - while (loadingDasSources) - { - try - { - Thread.sleep(5); - } catch (Exception e) - { - } - ; - } - ; - } - } - - Vector selected = new Vector(); - for (String source : selectedSources) - { - jalviewSourceI srce = sourceRegistry.getSource(source); - if (srce != null) - { - selected.addElement(srce); - } - } - return selected; - } - - @Override - public void refresh_actionPerformed(ActionEvent e) - { - saveProperties(jalview.bin.Cache.applicationProperties); - - Thread worker = new Thread(this); - worker.start(); - } - - private void setCapabilities(DasSourceRegistryI sourceRegistry2) - { - Vector authority = new Vector(); - Vector type = new Vector(); - Vector label = new Vector(); - Vector taxIds = new Vector(); - authority.add("Any"); - type.add("Any"); - label.add("Any"); - - for (jalviewSourceI ds : sourceRegistry2.getSources()) - { - VERSION latest = ds.getVersion(); - - for (COORDINATES cs : latest.getCOORDINATES()) - { - if (!type.contains(cs.getSource())) - { - type.add(cs.getSource()); // source==category - } - - if (!authority.contains(cs.getAuthority())) - { - authority.add(cs.getAuthority()); - } - } - - for (PROP slabel : latest.getPROP()) - { - if (slabel.getName().equalsIgnoreCase("LABEL") - && !label.contains(slabel.getValue())) - { - label.add(slabel.getValue()); - } - } - - } - - filter1.setListData(authority); - filter2.setListData(type); - filter3.setListData(label); - // filter4 taxIds - - javax.swing.SwingUtilities.invokeLater(new Runnable() - { - @Override - public void run() - { - filter1.setSelectedIndex(0); - filter2.setSelectedIndex(0); - filter3.setSelectedIndex(0); - } - }); - } - - @Override - public void amendLocal(boolean newSource) - { - String url = "http://localhost:8080/", nickname = ""; - boolean seqsrc = false; - if (!newSource) - { - int selectedRow = table.getSelectionModel().getMinSelectionIndex(); - nickname = table.getValueAt(selectedRow, 0).toString(); - jalviewSourceI source = sourceRegistry.getSource(nickname); - url = source.getUri(); - seqsrc = source.isSequenceSource(); - } - - JTextField nametf = new JTextField(nickname, 40); - JTextField urltf = new JTextField(url, 40); - JCheckBox seqs = new JCheckBox( - MessageManager.getString("label.sequence_source")); - seqs.setSelected(seqsrc); - JPanel panel = new JPanel(new BorderLayout()); - JPanel pane12 = new JPanel(new BorderLayout()); - pane12.add(new JLabel(MessageManager.getString("label.name:")), - BorderLayout.CENTER); - pane12.add(nametf, BorderLayout.EAST); - panel.add(pane12, BorderLayout.NORTH); - pane12 = new JPanel(new BorderLayout()); - pane12.add(new JLabel(MessageManager.getString("label.url:")), - BorderLayout.NORTH); - pane12.add(seqs, BorderLayout.SOUTH); - pane12.add(urltf, BorderLayout.EAST); - panel.add(pane12, BorderLayout.SOUTH); - - int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop, - panel, MessageManager.getString("label.enter_local_das_source"), - JvOptionPane.OK_CANCEL_OPTION); - - if (reply != JvOptionPane.OK_OPTION) - { - return; - } - - if (!urltf.getText().endsWith("/")) - { - urltf.setText(urltf.getText() + "/"); - } - - jalviewSourceI local = sourceRegistry.createLocalSource(urltf.getText(), - nametf.getText(), seqs.isSelected(), true); - List sources = sourceRegistry.getSources(); - int osize = sources.size(); - int size = osize + (newSource ? 1 : 0); - - Object[][] data = new Object[size][2]; - DASTableModel dtm = (table != null) - ? (DASTableModel) ((TableSorter) table.getModel()) - .getTableModel() - : null; - for (int i = 0; i < osize; i++) - { - String osrc = (dtm == null || i >= osize) ? null - : (String) dtm.getValueAt(i, 0); - if (!newSource && osrc != null - && dtm.getValueAt(i, 0).equals(nickname)) - { - data[i][0] = local.getTitle(); - data[i][1] = new Boolean(true); - } - else - { - data[i][0] = osrc; - data[i][1] = new Boolean(selectedSources.contains(osrc)); - } - } - // Always add a new source at the end - if (newSource) - { - data[osize][0] = local.getTitle(); - data[osize][1] = new Boolean(true); - selectedSources.add(local.getTitle()); - } - - refreshTableData(data); - - SwingUtilities.invokeLater(new Runnable() - { - @Override - public void run() - { - scrollPane.getVerticalScrollBar() - .setValue(scrollPane.getVerticalScrollBar().getMaximum()); - } - }); - - displayFullDetails(local.getTitle()); - } - - public void editRemoveLocalSource(MouseEvent evt) - { - int selectedRow = table.getSelectionModel().getMinSelectionIndex(); - if (selectedRow == -1) - { - return; - } - - String nickname = table.getValueAt(selectedRow, 0).toString(); - - if (!sourceRegistry.getSource(nickname).isLocal()) - { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, - MessageManager.getString( - "label.you_can_only_edit_or_remove_local_das_sources"), - MessageManager.getString("label.public_das_source"), - JvOptionPane.WARNING_MESSAGE); - return; - } - - Object[] options = { "Edit", "Remove", "Cancel" }; - int choice = JvOptionPane.showInternalOptionDialog(Desktop.desktop, - "Do you want to edit or remove " + nickname + "?", - "Edit / Remove Local DAS Source", - JvOptionPane.YES_NO_CANCEL_OPTION, - JvOptionPane.QUESTION_MESSAGE, null, options, options[2]); - - switch (choice) - { - case 0: - amendLocal(false); - break; - case 1: - sourceRegistry.removeLocalSource(sourceRegistry.getSource(nickname)); - selectedSources.remove(nickname); - Object[][] data = new Object[sourceRegistry.getSources().size()][2]; - int index = 0, l = table.getRowCount(); - - for (int i = 0; i < l; i++) - { - String nm; - if ((nm = (String) table.getValueAt(i, 0)).equals(nickname)) - { - continue; - } - else - { - data[index][0] = nm; - data[index][1] = new Boolean(selectedSources.contains(nm)); - index++; - } - } - refreshTableData(data); - SwingUtilities.invokeLater(new Runnable() - { - @Override - public void run() - { - scrollPane.getVerticalScrollBar() - .setValue(scrollPane.getVerticalScrollBar().getMaximum()); - } - }); - - break; - } - } - - @Override - public void valueChanged(ListSelectionEvent evt) - { - // Called when the MainTable selection changes - if (evt.getValueIsAdjusting()) - { - return; - } - - displayFullDetails(null); - - // Filter the displayed data sources - - ArrayList names = new ArrayList(); - ArrayList selected = new ArrayList(); - - // The features filter is not visible, but we must still - // filter the das source list here. - // July 2006 - only 6 sources fo not serve features - Object[] dummyFeatureList = new Object[] { "features" }; - List srcs = sourceRegistry.getSources(); - for (jalviewSourceI ds : srcs) - { - - VERSION v = ds.getVersion(); - List coords = v.getCOORDINATES(); - if (ds.isLocal() || ((coords == null || coords.size() == 0) - && filter1.getSelectedIndex() == 0 - && filter2.getSelectedIndex() == 0 - && filter3.getSelectedIndex() == 0)) - { - // THIS IS A FIX FOR LOCAL SOURCES WHICH DO NOT - // HAVE COORDINATE SYSTEMS, INFO WHICH AT PRESENT - // IS ADDED FROM THE REGISTRY - names.add(ds.getTitle()); - selected.add(new Boolean(selectedSources.contains(ds.getTitle()))); - continue; - } - - if (!selectedInList(dummyFeatureList, ds.getCapabilityList(v)) - || !selectedInList(filter3.getSelectedValues(), - ds.getLabelsFor(v))) - { - continue; - } - - for (int j = 0; j < coords.size(); j++) - { - if (selectedInList(filter1.getSelectedValues(), - new String[] - { coords.get(j).getAuthority() }) - && selectedInList(filter2.getSelectedValues(), new String[] - { coords.get(j).getSource() })) - { - names.add(ds.getTitle()); - selected.add( - new Boolean(selectedSources.contains(ds.getTitle()))); - break; - } - } - } - - int dSize = names.size(); - Object[][] data = new Object[dSize][2]; - for (int d = 0; d < dSize; d++) - { - data[d][0] = names.get(d); - data[d][1] = selected.get(d); - } - - refreshTableData(data); - } - - private boolean selectedInList(Object[] selection, String[] items) - { - for (int i = 0; i < selection.length; i++) - { - if (selection[i].equals("Any")) - { - return true; - } - if (items == null || items.length == 0) - { - return false; - } - String sel = (items[0].startsWith("das1:") ? "das1:" : "") - + selection[i]; - for (int j = 0; j < items.length; j++) - { - if (sel.equals(items[j])) - { - return true; - } - } - } - - return false; - } - - void setSelectedFromProperties() - { - String active = jalview.bin.Cache.getDefault("DAS_ACTIVE_SOURCE", - "uniprot"); - StringTokenizer st = new StringTokenizer(active, "\t"); - selectedSources = new Vector(); - while (st.hasMoreTokens()) - { - selectedSources.addElement(st.nextToken()); - } - } - - @Override - public void reset_actionPerformed(ActionEvent e) - { - registryURL.setText(sourceRegistry.getDasRegistryURL()); - } - - /** - * set the DAS source settings in the given jalview properties. - * - * @param properties - */ - public void saveProperties(Properties properties) - { - if (registryURL.getText() == null || registryURL.getText().length() < 1) - { - properties.remove(jalview.bin.Cache.DAS_REGISTRY_URL); - } - else - { - properties.setProperty(jalview.bin.Cache.DAS_REGISTRY_URL, - registryURL.getText()); - } - - StringBuffer sb = new StringBuffer(); - for (int r = 0; r < table.getModel().getRowCount(); r++) - { - if (((Boolean) table.getValueAt(r, 1)).booleanValue()) - { - sb.append(table.getValueAt(r, 0) + "\t"); - } - } - - properties.setProperty(jalview.bin.Cache.DAS_ACTIVE_SOURCE, - sb.toString()); - - String sourceprop = sourceRegistry.getLocalSourceString(); - properties.setProperty(jalview.bin.Cache.DAS_LOCAL_SOURCE, sourceprop); - } - - class DASTableModel extends AbstractTableModel - { - - public DASTableModel(Object[][] data) - { - this.data = data; - } - - private String[] columnNames = new String[] { - MessageManager.getString("label.nickname"), - MessageManager.getString("label.use_source") }; - - private Object[][] data; - - @Override - public int getColumnCount() - { - return columnNames.length; - } - - @Override - public int getRowCount() - { - return data.length; - } - - @Override - public String getColumnName(int col) - { - return columnNames[col]; - } - - @Override - public Object getValueAt(int row, int col) - { - return data[row][col]; - } - - /* - * JTable uses this method to determine the default renderer/ editor for - * each cell. If we didn't implement this method, then the last column would - * contain text ("true"/"false"), rather than a check box. - */ - @Override - public Class getColumnClass(int c) - { - return getValueAt(0, c).getClass(); - } - - /* - * Don't need to implement this method unless your table's editable. - */ - @Override - public boolean isCellEditable(int row, int col) - { - // Note that the data/cell address is constant, - // no matter where the cell appears onscreen. - return col == 1; - - } - - /* - * Don't need to implement this method unless your table's data can change. - */ - @Override - public void setValueAt(Object value, int row, int col) - { - data[row][col] = value; - fireTableCellUpdated(row, col); - - String name = getValueAt(row, 0).toString(); - boolean selected = ((Boolean) value).booleanValue(); - - if (selectedSources.contains(name) && !selected) - { - selectedSources.remove(name); - } - - if (!selectedSources.contains(name) && selected) - { - selectedSources.add(name); - } - } - } - - public void initDasSources() - { - - Thread thr = new Thread(new Runnable() - { - @Override - public void run() - { - // this actually initialises the das source list - paintComponent(null); // yuk - } - }); - thr.start(); - while (loadingDasSources || sourceRegistry == null) - { - try - { - Thread.sleep(10); - } catch (Exception e) - { - } - ; - } - } - - /** - * disable or enable the buttons on the source browser - * - * @param b - */ - public void setGuiEnabled(boolean b) - { - refresh.setEnabled(b); - addLocal.setEnabled(b); - } -} diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 128481c..8d9e366 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -32,6 +32,7 @@ import jalview.io.FileFormatException; import jalview.io.FileFormatI; import jalview.io.FileFormats; import jalview.io.FileLoader; +import jalview.io.FormatAdapter; import jalview.io.IdentifyFile; import jalview.io.JalviewFileChooser; import jalview.io.JalviewFileView; @@ -68,6 +69,7 @@ import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -91,10 +93,13 @@ import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultDesktopManager; import javax.swing.DesktopManager; +import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; @@ -116,6 +121,8 @@ import javax.swing.event.InternalFrameEvent; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; +import org.stackoverflowusers.file.WindowsShortcut; + /** * Jalview Desktop * @@ -512,7 +519,7 @@ public class Desktop extends jalview.jbgui.GDesktop { final Desktop me = this; // Thread off the news reader, in case there are connection problems. - addDialogThread(new Runnable() + new Thread(new Runnable() { @Override public void run() @@ -523,13 +530,13 @@ public class Desktop extends jalview.jbgui.GDesktop showNews.setVisible(true); Cache.log.debug("Completed news thread."); } - }); + }).start(); } public void getIdentifiersOrgData() { // Thread off the identifiers fetcher - addDialogThread(new Runnable() + new Thread(new Runnable() { @Override public void run() @@ -546,7 +553,8 @@ public class Desktop extends jalview.jbgui.GDesktop + e.getMessage()); } } - }); + }).start(); + ; } @Override @@ -849,6 +857,7 @@ public class Desktop extends jalview.jbgui.GDesktop frame.setResizable(resizable); frame.setMaximizable(resizable); frame.setIconifiable(resizable); + frame.setOpaque(false); if (frame.getX() < 1 && frame.getY() < 1) { @@ -899,8 +908,6 @@ public class Desktop extends jalview.jbgui.GDesktop menuItem.removeActionListener(menuItem.getActionListeners()[0]); } windowMenu.remove(menuItem); - - System.gc(); }; }); @@ -920,6 +927,8 @@ public class Desktop extends jalview.jbgui.GDesktop } }); + setKeyBindings(frame); + desktop.add(frame); windowMenu.add(menuItem); @@ -939,6 +948,42 @@ public class Desktop extends jalview.jbgui.GDesktop } } + /** + * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close + * the window + * + * @param frame + */ + private static void setKeyBindings(JInternalFrame frame) + { + @SuppressWarnings("serial") + final Action closeAction = new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + frame.dispose(); + } + }; + + /* + * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action + */ + KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, + InputEvent.CTRL_DOWN_MASK); + KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); + + InputMap inputMap = frame + .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + String ctrlW = ctrlWKey.toString(); + inputMap.put(ctrlWKey, ctrlW); + inputMap.put(cmdWKey, ctrlW); + + ActionMap actionMap = frame.getActionMap(); + actionMap.put(ctrlW, closeAction); + } + @Override public void lostOwnership(Clipboard clipboard, Transferable contents) { @@ -1388,7 +1433,6 @@ public class Desktop extends jalview.jbgui.GDesktop { ssm.resetAll(); } - System.gc(); } @Override @@ -3293,13 +3337,67 @@ public class Desktop extends jalview.jbgui.GDesktop return groovyConsole; } + /** + * handles the payload of a drag and drop event. + * + * TODO refactor to desktop utilities class + * + * @param files + * - Data source strings extracted from the drop event + * @param protocols + * - protocol for each data source extracted from the drop event + * @param evt + * - the drop event + * @param t + * - the payload from the drop event + * @throws Exception + */ public static void transferFromDropTarget(List files, List protocols, DropTargetDropEvent evt, Transferable t) throws Exception { DataFlavor uriListFlavor = new DataFlavor( - "text/uri-list;class=java.lang.String"); + "text/uri-list;class=java.lang.String"), urlFlavour = null; + try + { + urlFlavour = new DataFlavor( + "application/x-java-url; class=java.net.URL"); + } catch (ClassNotFoundException cfe) + { + Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe); + } + + if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) + { + + try + { + java.net.URL url = (URL) t.getTransferData(urlFlavour); + // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099 + // means url may be null. + if (url != null) + { + protocols.add(DataSourceType.URL); + files.add(url.toString()); + Cache.log.debug("Drop handled as URL dataflavor " + + files.get(files.size() - 1)); + return; + } + else + { + if (Platform.isAMac()) + { + System.err.println( + "Please ignore plist error - occurs due to problem with java 8 on OSX"); + } + ; + } + } catch (Throwable ex) + { + Cache.log.debug("URL drop handler failed.", ex); + } + } if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { // Works on Windows and MacOSX @@ -3327,63 +3425,112 @@ public class Desktop extends jalview.jbgui.GDesktop // fallback to text: workaround - on OSX where there's a JVM bug Cache.log.debug("standard URIListFlavor failed. Trying text"); // try text fallback - data = (String) t.getTransferData( - new DataFlavor("text/plain;class=java.lang.String")); - if (Cache.log.isDebugEnabled()) + DataFlavor textDf = new DataFlavor( + "text/plain;class=java.lang.String"); + if (t.isDataFlavorSupported(textDf)) { - Cache.log.debug("fallback returned " + data); + data = (String) t.getTransferData(textDf); } + + Cache.log.debug("Plain text drop content returned " + + (data == null ? "Null - failed" : data)); + } - while (protocols.size() < files.size()) - { - Cache.log.debug("Adding missing FILE protocol for " - + files.get(protocols.size())); - protocols.add(DataSourceType.FILE); - } - for (java.util.StringTokenizer st = new java.util.StringTokenizer( - data, "\r\n"); st.hasMoreTokens();) + if (data != null) { - added = true; - String s = st.nextToken(); - if (s.startsWith("#")) + while (protocols.size() < files.size()) { - // the line is a comment (as per the RFC 2483) - continue; + Cache.log.debug("Adding missing FILE protocol for " + + files.get(protocols.size())); + protocols.add(DataSourceType.FILE); } - java.net.URI uri = new java.net.URI(s); - if (uri.getScheme().toLowerCase().startsWith("http")) + for (java.util.StringTokenizer st = new java.util.StringTokenizer( + data, "\r\n"); st.hasMoreTokens();) { - protocols.add(DataSourceType.URL); - files.add(uri.toString()); - } - else - { - // otherwise preserve old behaviour: catch all for file objects - java.io.File file = new java.io.File(uri); - protocols.add(DataSourceType.FILE); - files.add(file.toString()); + added = true; + String s = st.nextToken(); + if (s.startsWith("#")) + { + // the line is a comment (as per the RFC 2483) + continue; + } + java.net.URI uri = new java.net.URI(s); + if (uri.getScheme().toLowerCase().startsWith("http")) + { + protocols.add(DataSourceType.URL); + files.add(uri.toString()); + } + else + { + // otherwise preserve old behaviour: catch all for file objects + java.io.File file = new java.io.File(uri); + protocols.add(DataSourceType.FILE); + files.add(file.toString()); + } } } + if (Cache.log.isDebugEnabled()) { if (data == null || !added) { - Cache.log.debug( - "Couldn't resolve drop data. Here are the supported flavors:"); - for (DataFlavor fl : t.getTransferDataFlavors()) + + if (t.getTransferDataFlavors() != null + && t.getTransferDataFlavors().length > 0) { Cache.log.debug( - "Supported transfer dataflavor: " + fl.toString()); - Object df = t.getTransferData(fl); - if (df != null) - { - Cache.log.debug("Retrieves: " + df); - } - else + "Couldn't resolve drop data. Here are the supported flavors:"); + for (DataFlavor fl : t.getTransferDataFlavors()) { - Cache.log.debug("Retrieved nothing"); + Cache.log.debug( + "Supported transfer dataflavor: " + fl.toString()); + Object df = t.getTransferData(fl); + if (df != null) + { + Cache.log.debug("Retrieves: " + df); + } + else + { + Cache.log.debug("Retrieved nothing"); + } } } + else + { + Cache.log.debug("Couldn't resolve dataflavor for drop: " + + t.toString()); + } + } + } + } + if (Platform.isWindows()) + + { + Cache.log.debug("Scanning dropped content for Windows Link Files"); + + // resolve any .lnk files in the file drop + for (int f = 0; f < files.size(); f++) + { + String source = files.get(f).toLowerCase(); + if (protocols.get(f).equals(DataSourceType.FILE) + && (source.endsWith(".lnk") || source.endsWith(".url") + || source.endsWith(".site"))) + { + try { + File lf = new File(files.get(f)); + // process link file to get a URL + Cache.log.debug("Found potential link file: " + lf); + WindowsShortcut wscfile = new WindowsShortcut(lf); + String fullname = wscfile.getRealFilename(); + protocols.set(f, FormatAdapter.checkProtocol(fullname)); + files.set(f, fullname); + Cache.log.debug("Parsed real filename " + fullname + + " to extract protocol: " + protocols.get(f)); + } + catch (Exception ex) + { + Cache.log.error("Couldn't parse "+files.get(f)+" as a link file.",ex); + } } } } @@ -3398,4 +3545,41 @@ public class Desktop extends jalview.jbgui.GDesktop { Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected)); } + + /** + * Answers a (possibly empty) list of any structure viewer frames (currently + * for either Jmol or Chimera) which are currently open. This may optionally + * be restricted to viewers of a specified class, or viewers linked to a + * specified alignment panel. + * + * @param apanel + * if not null, only return viewers linked to this panel + * @param structureViewerClass + * if not null, only return viewers of this class + * @return + */ + public List getStructureViewers( + AlignmentPanel apanel, + Class structureViewerClass) + { + List result = new ArrayList<>(); + JInternalFrame[] frames = Desktop.instance.getAllFrames(); + + for (JInternalFrame frame : frames) + { + if (frame instanceof StructureViewerBase) + { + if (structureViewerClass == null + || structureViewerClass.isInstance(frame)) + { + if (apanel == null + || ((StructureViewerBase) frame).isLinkedWith(apanel)) + { + result.add((StructureViewerBase) frame); + } + } + } + } + return result; + } } diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java deleted file mode 100644 index d8db546..0000000 --- a/src/jalview/gui/FeatureColourChooser.java +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.gui; - -import jalview.api.FeatureColourI; -import jalview.datamodel.GraphLine; -import jalview.schemes.FeatureColour; -import jalview.util.MessageManager; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -import javax.swing.BorderFactory; -import javax.swing.JCheckBox; -import javax.swing.JColorChooser; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSlider; -import javax.swing.JTextField; -import javax.swing.border.LineBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -public class FeatureColourChooser extends JalviewDialog -{ - // FeatureSettings fs; - private FeatureRenderer fr; - - private FeatureColourI cs; - - private FeatureColourI oldcs; - - private AlignmentPanel ap; - - private boolean adjusting = false; - - final private float min; - - final private float max; - - final private float scaleFactor; - - private String type = null; - - private JPanel minColour = new JPanel(); - - private JPanel maxColour = new JPanel(); - - private JComboBox threshold = new JComboBox<>(); - - private JSlider slider = new JSlider(); - - private JTextField thresholdValue = new JTextField(20); - - // TODO implement GUI for tolower flag - // JCheckBox toLower = new JCheckBox(); - - private JCheckBox thresholdIsMin = new JCheckBox(); - - private JCheckBox colourByLabel = new JCheckBox(); - - private GraphLine threshline; - - private Color oldmaxColour; - - private Color oldminColour; - - private ActionListener colourEditor = null; - - /** - * Constructor - * - * @param frender - * @param theType - */ - public FeatureColourChooser(FeatureRenderer frender, String theType) - { - this(frender, false, theType); - } - - /** - * Constructor, with option to make a blocking dialog (has to complete in the - * AWT event queue thread). Currently this option is always set to false. - * - * @param frender - * @param blocking - * @param theType - */ - FeatureColourChooser(FeatureRenderer frender, boolean blocking, - String theType) - { - this.fr = frender; - this.type = theType; - ap = fr.ap; - String title = MessageManager - .formatMessage("label.graduated_color_for_params", new String[] - { theType }); - initDialogFrame(this, true, blocking, title, 480, 185); - - slider.addChangeListener(new ChangeListener() - { - @Override - public void stateChanged(ChangeEvent evt) - { - if (!adjusting) - { - thresholdValue.setText((slider.getValue() / scaleFactor) + ""); - sliderValueChanged(); - } - } - }); - slider.addMouseListener(new MouseAdapter() - { - @Override - public void mouseReleased(MouseEvent evt) - { - /* - * only update Overview and/or structure colouring - * when threshold slider drag ends (mouse up) - */ - if (ap != null) - { - ap.paintAlignment(true, true); - } - } - }); - - float mm[] = fr.getMinMax().get(theType)[0]; - min = mm[0]; - max = mm[1]; - - /* - * ensure scale factor allows a scaled range with - * 10 integer divisions ('ticks'); if we have got here, - * we should expect that max != min - */ - scaleFactor = (max == min) ? 1f : 100f / (max - min); - - oldcs = fr.getFeatureColours().get(theType); - if (!oldcs.isSimpleColour()) - { - if (oldcs.isAutoScaled()) - { - // update the scale - cs = new FeatureColour((FeatureColour) oldcs, min, max); - } - else - { - cs = new FeatureColour((FeatureColour) oldcs); - } - } - else - { - // promote original color to a graduated color - Color bl = oldcs.getColour(); - if (bl == null) - { - bl = Color.BLACK; - } - // original colour becomes the maximum colour - cs = new FeatureColour(Color.white, bl, mm[0], mm[1]); - cs.setColourByLabel(false); - } - minColour.setBackground(oldminColour = cs.getMinColour()); - maxColour.setBackground(oldmaxColour = cs.getMaxColour()); - adjusting = true; - - try - { - jbInit(); - } catch (Exception ex) - { - } - // update the gui from threshold state - thresholdIsMin.setSelected(!cs.isAutoScaled()); - colourByLabel.setSelected(cs.isColourByLabel()); - if (cs.hasThreshold()) - { - // initialise threshold slider and selector - threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2); - slider.setEnabled(true); - slider.setValue((int) (cs.getThreshold() * scaleFactor)); - thresholdValue.setEnabled(true); - threshline = new GraphLine((max - min) / 2f, "Threshold", - Color.black); - threshline.value = cs.getThreshold(); - } - - adjusting = false; - - changeColour(false); - waitForInput(); - } - - private void jbInit() throws Exception - { - - minColour.setFont(JvSwingUtils.getLabelFont()); - minColour.setBorder(BorderFactory.createLineBorder(Color.black)); - minColour.setPreferredSize(new Dimension(40, 20)); - minColour.setToolTipText(MessageManager.getString("label.min_colour")); - minColour.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent e) - { - if (minColour.isEnabled()) - { - minColour_actionPerformed(); - } - } - }); - maxColour.setFont(JvSwingUtils.getLabelFont()); - maxColour.setBorder(BorderFactory.createLineBorder(Color.black)); - maxColour.setPreferredSize(new Dimension(40, 20)); - maxColour.setToolTipText(MessageManager.getString("label.max_colour")); - maxColour.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent e) - { - if (maxColour.isEnabled()) - { - maxColour_actionPerformed(); - } - } - }); - maxColour.setBorder(new LineBorder(Color.black)); - JLabel minText = new JLabel(MessageManager.getString("label.min")); - minText.setFont(JvSwingUtils.getLabelFont()); - JLabel maxText = new JLabel(MessageManager.getString("label.max")); - maxText.setFont(JvSwingUtils.getLabelFont()); - this.setLayout(new BorderLayout()); - JPanel jPanel1 = new JPanel(); - jPanel1.setBackground(Color.white); - JPanel jPanel2 = new JPanel(); - jPanel2.setLayout(new FlowLayout()); - jPanel2.setBackground(Color.white); - threshold.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - threshold_actionPerformed(); - } - }); - threshold.setToolTipText(MessageManager - .getString("label.threshold_feature_display_by_score")); - threshold.addItem(MessageManager - .getString("label.threshold_feature_no_threshold")); // index 0 - threshold.addItem(MessageManager - .getString("label.threshold_feature_above_threshold")); // index 1 - threshold.addItem(MessageManager - .getString("label.threshold_feature_below_threshold")); // index 2 - - JPanel jPanel3 = new JPanel(); - jPanel3.setLayout(new FlowLayout()); - thresholdValue.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - thresholdValue_actionPerformed(); - } - }); - thresholdValue.addFocusListener(new FocusAdapter() - { - @Override - public void focusLost(FocusEvent e) - { - thresholdValue_actionPerformed(); - } - }); - slider.setPaintLabels(false); - slider.setPaintTicks(true); - slider.setBackground(Color.white); - slider.setEnabled(false); - slider.setOpaque(false); - slider.setPreferredSize(new Dimension(100, 32)); - slider.setToolTipText( - MessageManager.getString("label.adjust_threshold")); - thresholdValue.setEnabled(false); - thresholdValue.setColumns(7); - jPanel3.setBackground(Color.white); - thresholdIsMin.setBackground(Color.white); - thresholdIsMin - .setText(MessageManager.getString("label.threshold_minmax")); - thresholdIsMin.setToolTipText(MessageManager - .getString("label.toggle_absolute_relative_display_threshold")); - thresholdIsMin.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent actionEvent) - { - thresholdIsMin_actionPerformed(); - } - }); - colourByLabel.setBackground(Color.white); - colourByLabel - .setText(MessageManager.getString("label.colour_by_label")); - colourByLabel.setToolTipText(MessageManager.getString( - "label.display_features_same_type_different_label_using_different_colour")); - colourByLabel.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent actionEvent) - { - colourByLabel_actionPerformed(); - } - }); - - JPanel colourPanel = new JPanel(); - colourPanel.setBackground(Color.white); - jPanel1.add(ok); - jPanel1.add(cancel); - jPanel2.add(colourByLabel, BorderLayout.WEST); - jPanel2.add(colourPanel, BorderLayout.EAST); - colourPanel.add(minText); - colourPanel.add(minColour); - colourPanel.add(maxText); - colourPanel.add(maxColour); - this.add(jPanel3, BorderLayout.CENTER); - jPanel3.add(threshold); - jPanel3.add(slider); - jPanel3.add(thresholdValue); - jPanel3.add(thresholdIsMin); - this.add(jPanel1, BorderLayout.SOUTH); - this.add(jPanel2, BorderLayout.NORTH); - } - - /** - * Action on clicking the 'minimum colour' - open a colour chooser dialog, and - * set the selected colour (if the user does not cancel out of the dialog) - */ - protected void minColour_actionPerformed() - { - Color col = JColorChooser.showDialog(this, - MessageManager.getString("label.select_colour_minimum_value"), - minColour.getBackground()); - if (col != null) - { - minColour.setBackground(col); - minColour.setForeground(col); - } - minColour.repaint(); - changeColour(true); - } - - /** - * Action on clicking the 'maximum colour' - open a colour chooser dialog, and - * set the selected colour (if the user does not cancel out of the dialog) - */ - protected void maxColour_actionPerformed() - { - Color col = JColorChooser.showDialog(this, - MessageManager.getString("label.select_colour_maximum_value"), - maxColour.getBackground()); - if (col != null) - { - maxColour.setBackground(col); - maxColour.setForeground(col); - } - maxColour.repaint(); - changeColour(true); - } - - /** - * Constructs and sets the selected colour options as the colour for the - * feature type, and repaints the alignment, and optionally the Overview - * and/or structure viewer if open - * - * @param updateStructsAndOverview - */ - void changeColour(boolean updateStructsAndOverview) - { - // Check if combobox is still adjusting - if (adjusting) - { - return; - } - - boolean aboveThreshold = false; - boolean belowThreshold = false; - if (threshold.getSelectedIndex() == 1) - { - aboveThreshold = true; - } - else if (threshold.getSelectedIndex() == 2) - { - belowThreshold = true; - } - boolean hasThreshold = aboveThreshold || belowThreshold; - - slider.setEnabled(true); - thresholdValue.setEnabled(true); - - FeatureColourI acg; - if (cs.isColourByLabel()) - { - acg = new FeatureColour(oldminColour, oldmaxColour, min, max); - } - else - { - acg = new FeatureColour(oldminColour = minColour.getBackground(), - oldmaxColour = maxColour.getBackground(), min, max); - } - - if (!hasThreshold) - { - slider.setEnabled(false); - thresholdValue.setEnabled(false); - thresholdValue.setText(""); - thresholdIsMin.setEnabled(false); - } - else if (threshline == null) - { - /* - * todo not yet implemented: visual indication of feature threshold - */ - threshline = new GraphLine((max - min) / 2f, "Threshold", - Color.black); - } - - if (hasThreshold) - { - adjusting = true; - acg.setThreshold(threshline.value); - - float range = (max - min) * scaleFactor; - - slider.setMinimum((int) (min * scaleFactor)); - slider.setMaximum((int) (max * scaleFactor)); - // slider.setValue((int) (threshline.value * scaleFactor)); - slider.setValue(Math.round(threshline.value * scaleFactor)); - thresholdValue.setText(threshline.value + ""); - slider.setMajorTickSpacing((int) (range / 10f)); - slider.setEnabled(true); - thresholdValue.setEnabled(true); - thresholdIsMin.setEnabled(!colourByLabel.isSelected()); - adjusting = false; - } - - acg.setAboveThreshold(aboveThreshold); - acg.setBelowThreshold(belowThreshold); - if (thresholdIsMin.isSelected() && hasThreshold) - { - acg.setAutoScaled(false); - if (aboveThreshold) - { - acg = new FeatureColour((FeatureColour) acg, threshline.value, max); - } - else - { - acg = new FeatureColour((FeatureColour) acg, min, threshline.value); - } - } - else - { - acg.setAutoScaled(true); - } - acg.setColourByLabel(colourByLabel.isSelected()); - if (acg.isColourByLabel()) - { - maxColour.setEnabled(false); - minColour.setEnabled(false); - maxColour.setBackground(this.getBackground()); - maxColour.setForeground(this.getBackground()); - minColour.setBackground(this.getBackground()); - minColour.setForeground(this.getBackground()); - - } - else - { - maxColour.setEnabled(true); - minColour.setEnabled(true); - maxColour.setBackground(oldmaxColour); - minColour.setBackground(oldminColour); - maxColour.setForeground(oldmaxColour); - minColour.setForeground(oldminColour); - } - fr.setColour(type, acg); - cs = acg; - ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); - } - - @Override - protected void raiseClosed() - { - if (this.colourEditor != null) - { - colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED")); - } - } - - @Override - public void okPressed() - { - changeColour(false); - } - - @Override - public void cancelPressed() - { - reset(); - } - - /** - * Action when the user cancels the dialog. All previous settings should be - * restored and rendered on the alignment, and any linked Overview window or - * structure. - */ - void reset() - { - fr.setColour(type, oldcs); - ap.paintAlignment(true, true); - cs = null; - } - - /** - * Action on change of choice of No / Above / Below Threshold - */ - protected void threshold_actionPerformed() - { - changeColour(true); - } - - /** - * Action on text entry of a threshold value - */ - protected void thresholdValue_actionPerformed() - { - try - { - float f = Float.parseFloat(thresholdValue.getText()); - slider.setValue((int) (f * scaleFactor)); - threshline.value = f; - - /* - * force repaint of any Overview window or structure - */ - ap.paintAlignment(true, true); - } catch (NumberFormatException ex) - { - } - } - - /** - * Action on change of threshold slider value. This may be done interactively - * (by moving the slider), or programmatically (to update the slider after - * manual input of a threshold value). - */ - protected void sliderValueChanged() - { - /* - * squash rounding errors by forcing min/max of slider to - * actual min/max of feature score range - */ - int value = slider.getValue(); - threshline.value = value == slider.getMaximum() ? max - : (value == slider.getMinimum() ? min : value / scaleFactor); - cs.setThreshold(threshline.value); - - /* - * repaint alignment, but not Overview or structure, - * to avoid overload while dragging the slider - */ - changeColour(false); - } - - protected void thresholdIsMin_actionPerformed() - { - changeColour(true); - } - - protected void colourByLabel_actionPerformed() - { - changeColour(true); - } - - void addActionListener(ActionListener graduatedColorEditor) - { - if (colourEditor != null) - { - System.err.println( - "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); - } - colourEditor = graduatedColorEditor; - } - - /** - * Answers the last colour setting selected by user - either oldcs (which may - * be a java.awt.Color) or the new GraduatedColor - * - * @return - */ - FeatureColourI getLastColour() - { - if (cs == null) - { - return oldcs; - } - return cs; - } - -} diff --git a/src/jalview/gui/FeatureRenderer.java b/src/jalview/gui/FeatureRenderer.java index 9c4b009..46f574e 100644 --- a/src/jalview/gui/FeatureRenderer.java +++ b/src/jalview/gui/FeatureRenderer.java @@ -180,15 +180,15 @@ public class FeatureRenderer final JSpinner end = new JSpinner(); start.setPreferredSize(new Dimension(80, 20)); end.setPreferredSize(new Dimension(80, 20)); - final FeatureRenderer me = this; final JLabel colour = new JLabel(); colour.setOpaque(true); // colour.setBorder(BorderFactory.createEtchedBorder()); colour.setMaximumSize(new Dimension(30, 16)); colour.addMouseListener(new MouseAdapter() { - FeatureColourChooser fcc = null; - + /* + * open colour chooser on click in colour panel + */ @Override public void mousePressed(MouseEvent evt) { @@ -205,28 +205,26 @@ public class FeatureRenderer } else { - if (fcc == null) + /* + * variable colour dialog - on OK, refetch the updated + * feature colour and update this display + */ + final String ft = features.get(featureIndex).getType(); + final String type = ft == null ? lastFeatureAdded : ft; + FeatureTypeSettings fcc = new FeatureTypeSettings( + FeatureRenderer.this, type); + fcc.setRequestFocusEnabled(true); + fcc.requestFocus(); + fcc.addActionListener(new ActionListener() { - final String ft = features.get(featureIndex).getType(); - final String type = ft == null ? lastFeatureAdded : ft; - fcc = new FeatureColourChooser(me, type); - fcc.setRequestFocusEnabled(true); - fcc.requestFocus(); - - fcc.addActionListener(new ActionListener() + @Override + public void actionPerformed(ActionEvent e) { - - @Override - public void actionPerformed(ActionEvent e) - { - fcol = fcc.getLastColour(); - fcc = null; - setColour(type, fcol); - updateColourButton(mainPanel, colour, fcol); - } - }); - - } + fcol = FeatureRenderer.this.getFeatureStyle(ft); + setColour(type, fcol); + updateColourButton(mainPanel, colour, fcol); + } + }); } } }); diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 3f1d9c7..1358c8f 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -22,20 +22,21 @@ package jalview.gui; import jalview.api.FeatureColourI; import jalview.api.FeatureSettingsControllerI; -import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.gui.Help.HelpId; import jalview.io.JalviewFileChooser; import jalview.io.JalviewFileView; +import jalview.schemabinding.version2.Filter; import jalview.schemabinding.version2.JalviewUserColours; +import jalview.schemabinding.version2.MatcherSet; import jalview.schemes.FeatureColour; -import jalview.util.Format; import jalview.util.MessageManager; import jalview.util.Platform; -import jalview.util.QuickSort; -import jalview.viewmodel.AlignmentViewport; -import jalview.ws.dbsources.das.api.jalviewSourceI; +import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import java.awt.BorderLayout; import java.awt.Color; @@ -44,6 +45,7 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.GridLayout; +import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -61,13 +63,14 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Vector; import javax.help.HelpSetException; import javax.swing.AbstractCellEditor; @@ -86,36 +89,52 @@ import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSlider; -import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; public class FeatureSettings extends JPanel implements FeatureSettingsControllerI { - DasSourceBrowser dassourceBrowser; + private static final String SEQUENCE_FEATURE_COLOURS = MessageManager + .getString("label.sequence_feature_colours"); - jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher; + /* + * column indices of fields in Feature Settings table + */ + static final int TYPE_COLUMN = 0; + + static final int COLOUR_COLUMN = 1; + + static final int FILTER_COLUMN = 2; + + static final int SHOW_COLUMN = 3; - JPanel settingsPane = new JPanel(); + private static final int COLUMN_COUNT = 4; - JPanel dasSettingsPane = new JPanel(); + private static final int MIN_WIDTH = 400; + + private static final int MIN_HEIGHT = 400; final FeatureRenderer fr; public final AlignFrame af; + /* + * 'original' fields hold settings to restore on Cancel + */ Object[][] originalData; private float originalTransparency; + private Map originalFilters; + final JInternalFrame frame; JScrollPane scrollPane = new JScrollPane(); @@ -126,29 +145,47 @@ public class FeatureSettings extends JPanel JSlider transparency = new JSlider(); - JPanel transPanel = new JPanel(new GridLayout(1, 2)); - - private static final int MIN_WIDTH = 400; - - private static final int MIN_HEIGHT = 400; - - /** + /* * when true, constructor is still executing - so ignore UI events */ protected volatile boolean inConstruction = true; + int selectedRow = -1; + + JButton fetchDAS = new JButton(); + + JButton saveDAS = new JButton(); + + JButton cancelDAS = new JButton(); + + boolean resettingTable = false; + + /* + * true when Feature Settings are updating from feature renderer + */ + private boolean handlingUpdate = false; + + /* + * holds {featureCount, totalExtent} for each feature type + */ + Map typeWidth = null; + /** * Constructor * * @param af */ - public FeatureSettings(AlignFrame af) + public FeatureSettings(AlignFrame alignFrame) { - this.af = af; + this.af = alignFrame; fr = af.getFeatureRenderer(); - // allow transparency to be recovered - transparency.setMaximum(100 - - (int) ((originalTransparency = fr.getTransparency()) * 100)); + + // save transparency for restore on Cancel + originalTransparency = fr.getTransparency(); + int originalTransparencyAsPercent = (int) (originalTransparency * 100); + transparency.setMaximum(100 - originalTransparencyAsPercent); + + originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy try { @@ -163,25 +200,48 @@ public class FeatureSettings extends JPanel @Override public String getToolTipText(MouseEvent e) { - if (table.columnAtPoint(e.getPoint()) == 0) + String tip = null; + int column = table.columnAtPoint(e.getPoint()); + switch (column) { - /* - * Tooltip for feature name only - */ - return JvSwingUtils.wrapTooltip(true, MessageManager + case TYPE_COLUMN: + tip = JvSwingUtils.wrapTooltip(true, MessageManager .getString("label.feature_settings_click_drag")); + break; + case FILTER_COLUMN: + int row = table.rowAtPoint(e.getPoint()); + FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row, + column); + tip = o.isEmpty() + ? MessageManager.getString("label.filters_tooltip") + : o.toString(); + break; + default: + break; } - return null; + return tip; } }; table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12)); table.setFont(new Font("Verdana", Font.PLAIN, 12)); - table.setDefaultRenderer(Color.class, new ColorRenderer()); - - table.setDefaultEditor(Color.class, new ColorEditor(this)); + // table.setDefaultRenderer(Color.class, new ColorRenderer()); + // table.setDefaultEditor(Color.class, new ColorEditor(this)); + // table.setDefaultEditor(FeatureColour.class, new ColorEditor(this)); table.setDefaultRenderer(FeatureColour.class, new ColorRenderer()); + + table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this)); + table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer()); + + TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75, + new ColorRenderer(), new ColorEditor(this)); + table.addColumn(colourColumn); + + TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75, + new FilterRenderer(), new FilterEditor(this)); + table.addColumn(filterColumn); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.addMouseListener(new MouseAdapter() @@ -190,11 +250,12 @@ public class FeatureSettings extends JPanel public void mousePressed(MouseEvent evt) { selectedRow = table.rowAtPoint(evt.getPoint()); + String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN); if (evt.isPopupTrigger()) { - popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.getMinMax(), - evt.getX(), evt.getY()); + Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN); + popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(), + evt.getY()); } else if (evt.getClickCount() == 2) { @@ -202,8 +263,7 @@ public class FeatureSettings extends JPanel boolean toggleSelection = Platform.isControlDown(evt); boolean extendSelection = evt.isShiftDown(); fr.ap.alignFrame.avc.markColumnsContainingFeatures( - invertSelection, extendSelection, toggleSelection, - (String) table.getValueAt(selectedRow, 0)); + invertSelection, extendSelection, toggleSelection, type); } } @@ -214,9 +274,10 @@ public class FeatureSettings extends JPanel selectedRow = table.rowAtPoint(evt.getPoint()); if (evt.isPopupTrigger()) { - popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.getMinMax(), - evt.getX(), evt.getY()); + String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN); + Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN); + popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(), + evt.getY()); } } }); @@ -253,9 +314,6 @@ public class FeatureSettings extends JPanel // MessageManager.getString("label.feature_settings_click_drag"))); scrollPane.setViewportView(table); - dassourceBrowser = new DasSourceBrowser(this); - dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER); - if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder()) { fr.findAllFeatures(true); // display everything! @@ -272,8 +330,8 @@ public class FeatureSettings extends JPanel if (!fs.resettingTable && !fs.handlingUpdate) { fs.handlingUpdate = true; - fs.resetTable(null); // new groups may be added with new seuqence - // feature types only + fs.resetTable(null); + // new groups may be added with new sequence feature types only fs.handlingUpdate = false; } } @@ -286,13 +344,13 @@ public class FeatureSettings extends JPanel { Desktop.addInternalFrame(frame, MessageManager.getString("label.sequence_feature_settings"), - 475, 480); + 600, 480); } else { Desktop.addInternalFrame(frame, MessageManager.getString("label.sequence_feature_settings"), - 400, 450); + 600, 450); } frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); @@ -304,14 +362,13 @@ public class FeatureSettings extends JPanel javax.swing.event.InternalFrameEvent evt) { fr.removePropertyChangeListener(change); - dassourceBrowser.fs = null; }; }); frame.setLayer(JLayeredPane.PALETTE_LAYER); inConstruction = false; } - protected void popupSort(final int selectedRow, final String type, + protected void popupSort(final int rowSelected, final String type, final Object typeCol, final Map minmax, int x, int y) { @@ -351,84 +408,70 @@ public class FeatureSettings extends JPanel }); men.add(dens); - if (minmax != null) + + /* + * variable colour options include colour by label, by score, + * by selected attribute text, or attribute value + */ + final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem( + MessageManager.getString("label.variable_colour")); + mxcol.setSelected(!featureColour.isSimpleColour()); + men.add(mxcol); + mxcol.addActionListener(new ActionListener() { - final float[][] typeMinMax = minmax.get(type); - /* - * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); // - * this is broken at the moment and isn't that useful anyway! - * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new - * ActionListener() { - * - * public void actionPerformed(ActionEvent e) { - * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type, - * null); } else { minmax.put(type, typeMinMax); } } - * - * }); - * - * men.add(chb); - */ - if (typeMinMax != null && typeMinMax[0] != null) - { - // if (table.getValueAt(row, column)); - // graduated colourschemes for those where minmax exists for the - // positional features - final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem( - "Graduated Colour"); - mxcol.setSelected(!featureColour.isSimpleColour()); - men.add(mxcol); - mxcol.addActionListener(new ActionListener() - { - JColorChooser colorChooser; + JColorChooser colorChooser; - @Override - public void actionPerformed(ActionEvent e) + @Override + public void actionPerformed(ActionEvent e) + { + if (e.getSource() == mxcol) + { + if (featureColour.isSimpleColour()) { - if (e.getSource() == mxcol) - { - if (featureColour.isSimpleColour()) - { - FeatureColourChooser fc = new FeatureColourChooser(me.fr, - type); - fc.addActionListener(this); - } - else - { - // bring up simple color chooser - colorChooser = new JColorChooser(); - JDialog dialog = JColorChooser.createDialog(me, - "Select new Colour", true, // modal - colorChooser, this, // OK button handler - null); // no CANCEL button handler - colorChooser.setColor(featureColour.getMaxColour()); - dialog.setVisible(true); - } - } - else - { - if (e.getSource() instanceof FeatureColourChooser) - { - FeatureColourChooser fc = (FeatureColourChooser) e - .getSource(); - table.setValueAt(fc.getLastColour(), selectedRow, 1); - table.validate(); - } - else - { - // probably the color chooser! - table.setValueAt(new FeatureColour(colorChooser.getColor()), - selectedRow, 1); - table.validate(); - me.updateFeatureRenderer( - ((FeatureTableModel) table.getModel()).getData(), - false); - } - } + FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type); + fc.addActionListener(this); } - - }); + else + { + // bring up simple color chooser + colorChooser = new JColorChooser(); + String title = MessageManager + .getString("label.select_colour"); + JDialog dialog = JColorChooser.createDialog(me, + title, true, // modal + colorChooser, this, // OK button handler + null); // no CANCEL button handler + colorChooser.setColor(featureColour.getMaxColour()); + dialog.setVisible(true); + } + } + else + { + if (e.getSource() instanceof FeatureTypeSettings) + { + /* + * update after OK in feature colour dialog; the updated + * colour will have already been set in the FeatureRenderer + */ + FeatureColourI fci = fr.getFeatureColours().get(type); + table.setValueAt(fci, rowSelected, 1); + table.validate(); + } + else + { + // probably the color chooser! + table.setValueAt(new FeatureColour(colorChooser.getColor()), + rowSelected, 1); + table.validate(); + me.updateFeatureRenderer( + ((FeatureTableModel) table.getModel()).getData(), + false); + } + } } - } + + }); + JMenuItem selCols = new JMenuItem( MessageManager.getString("label.select_columns_containing")); selCols.addActionListener(new ActionListener() @@ -478,16 +521,6 @@ public class FeatureSettings extends JPanel men.show(table, x, y); } - /** - * true when Feature Settings are updating from feature renderer - */ - private boolean handlingUpdate = false; - - /** - * holds {featureCount, totalExtent} for each feature type - */ - Map typeWidth = null; - @Override synchronized public void discoverAllFeatureData() { @@ -549,8 +582,6 @@ public class FeatureSettings extends JPanel return visible; } - boolean resettingTable = false; - synchronized void resetTable(String[] groupChanged) { if (resettingTable) @@ -613,7 +644,7 @@ public class FeatureSettings extends JPanel } } - Object[][] data = new Object[displayableTypes.size()][3]; + Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT]; int dataIndex = 0; if (fr.hasRenderOrder()) @@ -636,9 +667,13 @@ public class FeatureSettings extends JPanel continue; } - data[dataIndex][0] = type; - data[dataIndex][1] = fr.getFeatureStyle(type); - data[dataIndex][2] = new Boolean( + data[dataIndex][TYPE_COLUMN] = type; + data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type); + FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type); + data[dataIndex][FILTER_COLUMN] = featureFilter == null + ? new FeatureMatcherSet() + : featureFilter; + data[dataIndex][SHOW_COLUMN] = new Boolean( af.getViewport().getFeaturesDisplayed().isVisible(type)); dataIndex++; displayableTypes.remove(type); @@ -652,27 +687,30 @@ public class FeatureSettings extends JPanel while (!displayableTypes.isEmpty()) { String type = displayableTypes.iterator().next(); - data[dataIndex][0] = type; + data[dataIndex][TYPE_COLUMN] = type; - data[dataIndex][1] = fr.getFeatureStyle(type); - if (data[dataIndex][1] == null) + data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type); + if (data[dataIndex][COLOUR_COLUMN] == null) { // "Colour has been updated in another view!!" fr.clearRenderOrder(); return; } - - data[dataIndex][2] = new Boolean(true); + FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type); + data[dataIndex][FILTER_COLUMN] = featureFilter == null + ? new FeatureMatcherSet() + : featureFilter; + data[dataIndex][SHOW_COLUMN] = new Boolean(true); dataIndex++; displayableTypes.remove(type); } if (originalData == null) { - originalData = new Object[data.length][3]; + originalData = new Object[data.length][COLUMN_COUNT]; for (int i = 0; i < data.length; i++) { - System.arraycopy(data[i], 0, originalData[i], 0, 3); + System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT); } } else @@ -693,8 +731,8 @@ public class FeatureSettings extends JPanel } /** - * Updates 'originalData' (used for restore on Cancel) if we detect that - * changes have been made outwith this dialog + * Updates 'originalData' (used for restore on Cancel) if we detect that changes + * have been made outwith this dialog *
            *
          • a new feature type added (and made visible)
          • *
          • a feature colour changed (in the Amend Features dialog)
          • @@ -710,27 +748,27 @@ public class FeatureSettings extends JPanel .getData(); for (Object[] row : foundData) { - String type = (String) row[0]; + String type = (String) row[TYPE_COLUMN]; boolean found = false; for (Object[] current : currentData) { - if (type.equals(current[0])) + if (type.equals(current[TYPE_COLUMN])) { found = true; /* * currently dependent on object equality here; * really need an equals method on FeatureColour */ - if (!row[1].equals(current[1])) + if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN])) { /* * feature colour has changed externally - update originalData */ for (Object[] original : originalData) { - if (type.equals(original[0])) + if (type.equals(original[TYPE_COLUMN])) { - original[1] = row[1]; + original[COLOUR_COLUMN] = row[COLOUR_COLUMN]; break; } } @@ -743,10 +781,12 @@ public class FeatureSettings extends JPanel /* * new feature detected - add to original data (on top) */ - Object[][] newData = new Object[originalData.length + 1][3]; + Object[][] newData = new Object[originalData.length + + 1][COLUMN_COUNT]; for (int i = 0; i < originalData.length; i++) { - System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3); + System.arraycopy(originalData[i], 0, newData[i + 1], 0, + COLUMN_COUNT); } newData[0] = row; originalData = newData; @@ -756,8 +796,8 @@ public class FeatureSettings extends JPanel /** * Remove from the groups panel any checkboxes for groups that are not in the - * foundGroups set. This enables removing a group from the display when the - * last feature in that group is deleted. + * foundGroups set. This enables removing a group from the display when the last + * feature in that group is deleted. * * @param foundGroups */ @@ -800,10 +840,14 @@ public class FeatureSettings extends JPanel } } + /** + * Offers a file chooser dialog, and then loads the feature colours and + * filters from file in XML format and unmarshals to Jalview feature settings + */ void load() { JalviewFileChooser chooser = new JalviewFileChooser("fc", - "Sequence Feature Colours"); + SEQUENCE_FEATURE_COLOURS); chooser.setFileView(new JalviewFileView()); chooser.setDialogTitle( MessageManager.getString("label.load_feature_colours")); @@ -814,88 +858,78 @@ public class FeatureSettings extends JPanel if (value == JalviewFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); + load(file); + } + } - try - { - InputStreamReader in = new InputStreamReader( - new FileInputStream(file), "UTF-8"); + /** + * Loads feature colours and filters from XML stored in the given file + * + * @param file + */ + void load(File file) + { + try + { + InputStreamReader in = new InputStreamReader( + new FileInputStream(file), "UTF-8"); - JalviewUserColours jucs = JalviewUserColours.unmarshal(in); + JalviewUserColours jucs = JalviewUserColours.unmarshal(in); - for (int i = jucs.getColourCount() - 1; i >= 0; i--) - { - String name; - jalview.schemabinding.version2.Colour newcol = jucs.getColour(i); - if (newcol.hasMax()) - { - Color mincol = null, maxcol = null; - try - { - mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16)); - maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16)); + /* + * load feature colours + */ + for (int i = jucs.getColourCount() - 1; i >= 0; i--) + { + jalview.schemabinding.version2.Colour newcol = jucs.getColour(i); + FeatureColourI colour = Jalview2XML.unmarshalColour(newcol); + fr.setColour(newcol.getName(), colour); + fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount()); + } - } catch (Exception e) - { - Cache.log.warn("Couldn't parse out graduated feature color.", - e); - } - FeatureColourI gcol = new FeatureColour(mincol, maxcol, - newcol.getMin(), newcol.getMax()); - if (newcol.hasAutoScale()) - { - gcol.setAutoScaled(newcol.getAutoScale()); - } - if (newcol.hasColourByLabel()) - { - gcol.setColourByLabel(newcol.getColourByLabel()); - } - if (newcol.hasThreshold()) - { - gcol.setThreshold(newcol.getThreshold()); - } - if (newcol.getThreshType().length() > 0) - { - String ttyp = newcol.getThreshType(); - if (ttyp.equalsIgnoreCase("ABOVE")) - { - gcol.setAboveThreshold(true); - } - if (ttyp.equalsIgnoreCase("BELOW")) - { - gcol.setBelowThreshold(true); - } - } - fr.setColour(name = newcol.getName(), gcol); - } - else - { - Color color = new Color( - Integer.parseInt(jucs.getColour(i).getRGB(), 16)); - fr.setColour(name = jucs.getColour(i).getName(), - new FeatureColour(color)); - } - fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount()); - } - if (table != null) + /* + * load feature filters; loaded filters will replace any that are + * currently defined, other defined filters are left unchanged + */ + for (int i = 0; i < jucs.getFilterCount(); i++) + { + jalview.schemabinding.version2.Filter filterModel = jucs + .getFilter(i); + String featureType = filterModel.getFeatureType(); + FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType, + filterModel.getMatcherSet()); + if (!filter.isEmpty()) { - resetTable(null); - Object[][] data = ((FeatureTableModel) table.getModel()) - .getData(); - ensureOrder(data); - updateFeatureRenderer(data, false); - table.repaint(); + fr.setFeatureFilter(featureType, filter); } - } catch (Exception ex) - { - System.out.println("Error loading User Colour File\n" + ex); } + + /* + * update feature settings table + */ + if (table != null) + { + resetTable(null); + Object[][] data = ((FeatureTableModel) table.getModel()) + .getData(); + ensureOrder(data); + updateFeatureRenderer(data, false); + table.repaint(); + } + } catch (Exception ex) + { + System.out.println("Error loading User Colour File\n" + ex); } } + /** + * Offers a file chooser dialog, and then saves the current feature colours + * and any filters to the selected file in XML format + */ void save() { JalviewFileChooser chooser = new JalviewFileChooser("fc", - "Sequence Feature Colours"); + SEQUENCE_FEATURE_COLOURS); chooser.setFileView(new JalviewFileView()); chooser.setDialogTitle( MessageManager.getString("label.save_feature_colours")); @@ -905,68 +939,87 @@ public class FeatureSettings extends JPanel if (value == JalviewFileChooser.APPROVE_OPTION) { - String choice = chooser.getSelectedFile().getPath(); - jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours(); - ucs.setSchemeName("Sequence Features"); - try - { - PrintWriter out = new PrintWriter(new OutputStreamWriter( - new FileOutputStream(choice), "UTF-8")); + save(chooser.getSelectedFile()); + } + } - Set fr_colours = fr.getAllFeatureColours(); - Iterator e = fr_colours.iterator(); - float[] sortOrder = new float[fr_colours.size()]; - String[] sortTypes = new String[fr_colours.size()]; - int i = 0; - while (e.hasNext()) + /** + * Saves feature colours and filters to the given file + * + * @param file + */ + void save(File file) + { + JalviewUserColours ucs = new JalviewUserColours(); + ucs.setSchemeName("Sequence Features"); + try + { + PrintWriter out = new PrintWriter(new OutputStreamWriter( + new FileOutputStream(file), "UTF-8")); + + /* + * sort feature types by colour order, from 0 (highest) + * to 1 (lowest) + */ + Set fr_colours = fr.getAllFeatureColours(); + String[] sortedTypes = fr_colours + .toArray(new String[fr_colours.size()]); + Arrays.sort(sortedTypes, new Comparator() + { + @Override + public int compare(String type1, String type2) { - sortTypes[i] = e.next(); - sortOrder[i] = fr.getOrder(sortTypes[i]); - i++; + return Float.compare(fr.getOrder(type1), fr.getOrder(type2)); } - QuickSort.sort(sortOrder, sortTypes); - sortOrder = null; - for (i = 0; i < sortTypes.length; i++) + }); + + /* + * save feature colours + */ + for (String featureType : sortedTypes) + { + FeatureColourI fcol = fr.getFeatureStyle(featureType); + jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour( + featureType, fcol); + ucs.addColour(col); + } + + /* + * save any feature filters + */ + for (String featureType : sortedTypes) + { + FeatureMatcherSetI filter = fr.getFeatureFilter(featureType); + if (filter != null && !filter.isEmpty()) { - jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour(); - col.setName(sortTypes[i]); - FeatureColourI fcol = fr.getFeatureStyle(sortTypes[i]); - if (fcol.isSimpleColour()) - { - col.setRGB(Format.getHexString(fcol.getColour())); - } - else - { - col.setRGB(Format.getHexString(fcol.getMaxColour())); - col.setMin(fcol.getMin()); - col.setMax(fcol.getMax()); - col.setMinRGB( - jalview.util.Format.getHexString(fcol.getMinColour())); - col.setAutoScale(fcol.isAutoScaled()); - col.setThreshold(fcol.getThreshold()); - col.setColourByLabel(fcol.isColourByLabel()); - col.setThreshType(fcol.isAboveThreshold() ? "ABOVE" - : (fcol.isBelowThreshold() ? "BELOW" : "NONE")); - } - ucs.addColour(col); + Iterator iterator = filter.getMatchers().iterator(); + FeatureMatcherI firstMatcher = iterator.next(); + MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator, + filter.isAnded()); + Filter filterModel = new Filter(); + filterModel.setFeatureType(featureType); + filterModel.setMatcherSet(ms); + ucs.addFilter(filterModel); } - ucs.marshal(out); - out.close(); - } catch (Exception ex) - { - ex.printStackTrace(); } + + ucs.marshal(out); + out.close(); + } catch (Exception ex) + { + ex.printStackTrace(); } } public void invertSelection() { - for (int i = 0; i < table.getRowCount(); i++) + Object[][] data = ((FeatureTableModel) table.getModel()).getData(); + for (int i = 0; i < data.length; i++) { - Boolean value = (Boolean) table.getValueAt(i, 2); - - table.setValueAt(new Boolean(!value.booleanValue()), i, 2); + data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN]; } + updateFeatureRenderer(data, true); + table.repaint(); } public void orderByAvWidth() @@ -979,17 +1032,16 @@ public class FeatureSettings extends JPanel float[] width = new float[data.length]; float[] awidth; float max = 0; - int num = 0; + for (int i = 0; i < data.length; i++) { - awidth = typeWidth.get(data[i][0]); + awidth = typeWidth.get(data[i][TYPE_COLUMN]); if (awidth[0] > 0) { width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better // weight - but have to make per // sequence, too (awidth[2]) // if (width[i]==1) // hack to distinguish single width sequences. - num++; } else { @@ -1006,16 +1058,17 @@ public class FeatureSettings extends JPanel // awidth = (float[]) typeWidth.get(data[i][0]); if (width[i] == 0) { - width[i] = fr.getOrder(data[i][0].toString()); + width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString()); if (width[i] < 0) { - width[i] = fr.setOrder(data[i][0].toString(), i / data.length); + width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(), + i / data.length); } } else { width[i] /= max; // normalize - fr.setOrder(data[i][0].toString(), width[i]); // store for later + fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later } if (i > 0) { @@ -1049,76 +1102,56 @@ public class FeatureSettings extends JPanel } /** - * Update the priority order of features; only repaint if this changed the - * order of visible features + * Update the priority order of features; only repaint if this changed the order + * of visible features * * @param data * @param visibleNew */ private void updateFeatureRenderer(Object[][] data, boolean visibleNew) { - if (fr.setFeaturePriority(data, visibleNew)) + FeatureSettingsBean[] rowData = getTableAsBeans(data); + + if (fr.setFeaturePriority(rowData, visibleNew)) { af.alignPanel.paintAlignment(true, true); } } - int selectedRow = -1; - - JTabbedPane tabbedPane = new JTabbedPane(); - - BorderLayout borderLayout1 = new BorderLayout(); - - BorderLayout borderLayout2 = new BorderLayout(); - - BorderLayout borderLayout3 = new BorderLayout(); - - JPanel bigPanel = new JPanel(); - - BorderLayout borderLayout4 = new BorderLayout(); - - JButton invert = new JButton(); - - JPanel buttonPanel = new JPanel(); - - JButton cancel = new JButton(); - - JButton ok = new JButton(); - - JButton loadColours = new JButton(); - - JButton saveColours = new JButton(); - - JPanel dasButtonPanel = new JPanel(); - - JButton fetchDAS = new JButton(); - - JButton saveDAS = new JButton(); - - JButton cancelDAS = new JButton(); - - JButton optimizeOrder = new JButton(); - - JButton sortByScore = new JButton(); - - JButton sortByDens = new JButton(); - - JButton help = new JButton(); - - JPanel transbuttons = new JPanel(new GridLayout(5, 1)); + /** + * Converts table data into an array of data beans + */ + private FeatureSettingsBean[] getTableAsBeans(Object[][] data) + { + FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length]; + for (int i = 0; i < data.length; i++) + { + String type = (String) data[i][TYPE_COLUMN]; + FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN]; + FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN]; + Boolean isShown = (Boolean) data[i][SHOW_COLUMN]; + rowData[i] = new FeatureSettingsBean(type, colour, theFilter, + isShown); + } + return rowData; + } private void jbInit() throws Exception { - this.setLayout(borderLayout1); - settingsPane.setLayout(borderLayout2); - dasSettingsPane.setLayout(borderLayout3); - bigPanel.setLayout(borderLayout4); + this.setLayout(new BorderLayout()); + + JPanel settingsPane = new JPanel(); + settingsPane.setLayout(new BorderLayout()); + + JPanel bigPanel = new JPanel(); + bigPanel.setLayout(new BorderLayout()); groupPanel = new JPanel(); bigPanel.add(groupPanel, BorderLayout.NORTH); + JButton invert = new JButton( + MessageManager.getString("label.invert_selection")); invert.setFont(JvSwingUtils.getLabelFont()); - invert.setText(MessageManager.getString("label.invert_selection")); invert.addActionListener(new ActionListener() { @Override @@ -1127,8 +1160,10 @@ public class FeatureSettings extends JPanel invertSelection(); } }); + + JButton optimizeOrder = new JButton( + MessageManager.getString("label.optimise_order")); optimizeOrder.setFont(JvSwingUtils.getLabelFont()); - optimizeOrder.setText(MessageManager.getString("label.optimise_order")); optimizeOrder.addActionListener(new ActionListener() { @Override @@ -1137,9 +1172,10 @@ public class FeatureSettings extends JPanel orderByAvWidth(); } }); + + JButton sortByScore = new JButton( + MessageManager.getString("label.seq_sort_by_score")); sortByScore.setFont(JvSwingUtils.getLabelFont()); - sortByScore - .setText(MessageManager.getString("label.seq_sort_by_score")); sortByScore.addActionListener(new ActionListener() { @Override @@ -1148,9 +1184,9 @@ public class FeatureSettings extends JPanel af.avc.sortAlignmentByFeatureScore(null); } }); - sortByDens.setFont(JvSwingUtils.getLabelFont()); - sortByDens.setText( + JButton sortByDens = new JButton( MessageManager.getString("label.sequence_sort_by_density")); + sortByDens.setFont(JvSwingUtils.getLabelFont()); sortByDens.addActionListener(new ActionListener() { @Override @@ -1159,8 +1195,9 @@ public class FeatureSettings extends JPanel af.avc.sortAlignmentByFeatureDensity(null); } }); + + JButton help = new JButton(MessageManager.getString("action.help")); help.setFont(JvSwingUtils.getLabelFont()); - help.setText(MessageManager.getString("action.help")); help.addActionListener(new ActionListener() { @Override @@ -1191,20 +1228,23 @@ public class FeatureSettings extends JPanel } } }); + + JButton cancel = new JButton(MessageManager.getString("action.cancel")); cancel.setFont(JvSwingUtils.getLabelFont()); - cancel.setText(MessageManager.getString("action.cancel")); cancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { fr.setTransparency(originalTransparency); + fr.setFeatureFilters(originalFilters); updateFeatureRenderer(originalData); close(); } }); + + JButton ok = new JButton(MessageManager.getString("action.ok")); ok.setFont(JvSwingUtils.getLabelFont()); - ok.setText(MessageManager.getString("action.ok")); ok.addActionListener(new ActionListener() { @Override @@ -1213,8 +1253,12 @@ public class FeatureSettings extends JPanel close(); } }); + + JButton loadColours = new JButton( + MessageManager.getString("label.load_colours")); loadColours.setFont(JvSwingUtils.getLabelFont()); - loadColours.setText(MessageManager.getString("label.load_colours")); + loadColours.setToolTipText( + MessageManager.getString("label.load_colours_tooltip")); loadColours.addActionListener(new ActionListener() { @Override @@ -1223,8 +1267,12 @@ public class FeatureSettings extends JPanel load(); } }); + + JButton saveColours = new JButton( + MessageManager.getString("label.save_colours")); saveColours.setFont(JvSwingUtils.getLabelFont()); - saveColours.setText(MessageManager.getString("label.save_colours")); + saveColours.setToolTipText( + MessageManager.getString("label.save_colours_tooltip")); saveColours.addActionListener(new ActionListener() { @Override @@ -1241,7 +1289,7 @@ public class FeatureSettings extends JPanel if (!inConstruction) { fr.setTransparency((100 - transparency.getValue()) / 100f); - af.alignPanel.paintAlignment(true,true); + af.alignPanel.paintAlignment(true, true); } } }); @@ -1249,219 +1297,28 @@ public class FeatureSettings extends JPanel transparency.setMaximum(70); transparency.setToolTipText( MessageManager.getString("label.transparency_tip")); - fetchDAS.setText(MessageManager.getString("label.fetch_das_features")); - fetchDAS.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - fetchDAS_actionPerformed(e); - } - }); - saveDAS.setText(MessageManager.getString("action.save_as_default")); - saveDAS.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - saveDAS_actionPerformed(e); - } - }); - dasButtonPanel.setBorder(BorderFactory.createEtchedBorder()); - dasSettingsPane.setBorder(null); - cancelDAS.setEnabled(false); - cancelDAS.setText(MessageManager.getString("action.cancel_fetch")); - cancelDAS.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - cancelDAS_actionPerformed(e); - } - }); - this.add(tabbedPane, java.awt.BorderLayout.CENTER); - tabbedPane.addTab(MessageManager.getString("label.feature_settings"), - settingsPane); - tabbedPane.addTab(MessageManager.getString("label.das_settings"), - dasSettingsPane); - bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH); + + JPanel transPanel = new JPanel(new GridLayout(1, 2)); + bigPanel.add(transPanel, BorderLayout.SOUTH); + + JPanel transbuttons = new JPanel(new GridLayout(5, 1)); transbuttons.add(optimizeOrder); transbuttons.add(invert); transbuttons.add(sortByScore); transbuttons.add(sortByDens); transbuttons.add(help); - JPanel sliderPanel = new JPanel(); - sliderPanel.add(transparency); transPanel.add(transparency); transPanel.add(transbuttons); + + JPanel buttonPanel = new JPanel(); buttonPanel.add(ok); buttonPanel.add(cancel); buttonPanel.add(loadColours); buttonPanel.add(saveColours); - bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER); - dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH); - dasButtonPanel.add(fetchDAS); - dasButtonPanel.add(cancelDAS); - dasButtonPanel.add(saveDAS); - settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER); - settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH); - } - - public void fetchDAS_actionPerformed(ActionEvent e) - { - fetchDAS.setEnabled(false); - cancelDAS.setEnabled(true); - dassourceBrowser.setGuiEnabled(false); - Vector selectedSources = dassourceBrowser - .getSelectedSources(); - doDasFeatureFetch(selectedSources, true, true); - } - - /** - * get the features from selectedSources for all or the current selection - * - * @param selectedSources - * @param checkDbRefs - * @param promptFetchDbRefs - */ - private void doDasFeatureFetch(List selectedSources, - boolean checkDbRefs, boolean promptFetchDbRefs) - { - SequenceI[] dataset, seqs; - int iSize; - AlignmentViewport vp = af.getViewport(); - if (vp.getSelectionGroup() != null - && vp.getSelectionGroup().getSize() > 0) - { - iSize = vp.getSelectionGroup().getSize(); - dataset = new SequenceI[iSize]; - seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment()); - } - else - { - iSize = vp.getAlignment().getHeight(); - seqs = vp.getAlignment().getSequencesArray(); - } - - dataset = new SequenceI[iSize]; - for (int i = 0; i < iSize; i++) - { - dataset[i] = seqs[i].getDatasetSequence(); - } - - cancelDAS.setEnabled(true); - dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset, - this, selectedSources, checkDbRefs, promptFetchDbRefs); - af.getViewport().setShowSequenceFeatures(true); - af.showSeqFeatures.setSelected(true); - } - - /** - * blocking call to initialise the das source browser - */ - public void initDasSources() - { - dassourceBrowser.initDasSources(); - } - - /** - * examine the current list of das sources and return any matching the given - * nicknames in sources - * - * @param sources - * Vector of Strings to resolve to DAS source nicknames. - * @return sources that are present in source list. - */ - public List resolveSourceNicknames(Vector sources) - { - return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources); - } - - /** - * get currently selected das sources. ensure you have called initDasSources - * before calling this. - * - * @return vector of selected das source nicknames - */ - public Vector getSelectedSources() - { - return dassourceBrowser.getSelectedSources(); - } - - /** - * properly initialise DAS fetcher and then initiate a new thread to fetch - * features from the named sources (rather than any turned on by default) - * - * @param sources - * @param block - * if true then runs in same thread, otherwise passes to the Swing - * executor - */ - public void fetchDasFeatures(Vector sources, boolean block) - { - initDasSources(); - List resolved = dassourceBrowser.sourceRegistry - .resolveSourceNicknames(sources); - if (resolved.size() == 0) - { - resolved = dassourceBrowser.getSelectedSources(); - } - if (resolved.size() > 0) - { - final List dassources = resolved; - fetchDAS.setEnabled(false); - // cancelDAS.setEnabled(true); doDasFetch does this. - Runnable fetcher = new Runnable() - { - - @Override - public void run() - { - doDasFeatureFetch(dassources, true, false); - - } - }; - if (block) - { - fetcher.run(); - } - else - { - SwingUtilities.invokeLater(fetcher); - } - } - } - - public void saveDAS_actionPerformed(ActionEvent e) - { - dassourceBrowser - .saveProperties(jalview.bin.Cache.applicationProperties); - } - - public void complete() - { - fetchDAS.setEnabled(true); - cancelDAS.setEnabled(false); - dassourceBrowser.setGuiEnabled(true); - - } - - public void cancelDAS_actionPerformed(ActionEvent e) - { - if (dasFeatureFetcher != null) - { - dasFeatureFetcher.cancel(); - } - complete(); - } - - public void noDasSourceActive() - { - complete(); - JvOptionPane.showInternalConfirmDialog(Desktop.desktop, - MessageManager.getString("label.no_das_sources_selected_warn"), - MessageManager.getString("label.no_das_sources_selected_title"), - JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE); + bigPanel.add(scrollPane, BorderLayout.CENTER); + settingsPane.add(bigPanel, BorderLayout.CENTER); + settingsPane.add(buttonPanel, BorderLayout.SOUTH); + this.add(settingsPane); } // /////////////////////////////////////////////////////////////////////// @@ -1469,18 +1326,19 @@ public class FeatureSettings extends JPanel // /////////////////////////////////////////////////////////////////////// class FeatureTableModel extends AbstractTableModel { - FeatureTableModel(Object[][] data) - { - this.data = data; - } - private String[] columnNames = { MessageManager.getString("label.feature_type"), MessageManager.getString("action.colour"), - MessageManager.getString("label.display") }; + MessageManager.getString("label.filter"), + MessageManager.getString("label.show") }; private Object[][] data; + FeatureTableModel(Object[][] data) + { + this.data = data; + } + public Object[][] getData() { return data; @@ -1520,10 +1378,14 @@ public class FeatureSettings extends JPanel return data[row][col]; } + /** + * Answers the class of the object in column c of the first row of the table + */ @Override - public Class getColumnClass(int c) + public Class getColumnClass(int c) { - return getValueAt(0, c).getClass(); + Object v = getValueAt(0, c); + return v == null ? null : v.getClass(); } @Override @@ -1562,12 +1424,7 @@ public class FeatureSettings extends JPanel boolean isSelected, boolean hasFocus, int row, int column) { FeatureColourI cellColour = (FeatureColourI) color; - // JLabel comp = new JLabel(); - // comp. setOpaque(true); - // comp. - // setBounds(getBounds()); - Color newColor; setToolTipText(baseTT); setBackground(tbl.getBackground()); if (!cellColour.isSimpleColour()) @@ -1575,15 +1432,61 @@ public class FeatureSettings extends JPanel Rectangle cr = tbl.getCellRect(row, column, false); FeatureSettings.renderGraduatedColor(this, cellColour, (int) cr.getWidth(), (int) cr.getHeight()); - } else { this.setText(""); this.setIcon(null); - newColor = cellColour.getColour(); - setBackground(newColor); + setBackground(cellColour.getColour()); + } + if (isSelected) + { + if (selectedBorder == null) + { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + tbl.getSelectionBackground()); + } + setBorder(selectedBorder); } + else + { + if (unselectedBorder == null) + { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + tbl.getBackground()); + } + setBorder(unselectedBorder); + } + + return this; + } + } + + class FilterRenderer extends JLabel implements TableCellRenderer + { + javax.swing.border.Border unselectedBorder = null; + + javax.swing.border.Border selectedBorder = null; + + public FilterRenderer() + { + setOpaque(true); // MUST do this for background to show up. + setHorizontalTextPosition(SwingConstants.CENTER); + setVerticalTextPosition(SwingConstants.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable tbl, + Object filter, boolean isSelected, boolean hasFocus, int row, + int column) + { + FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter; + setOpaque(true); + String asText = theFilter.toString(); + setBackground(tbl.getBackground()); + this.setText(asText); + this.setIcon(null); + if (isSelected) { if (selectedBorder == null) @@ -1633,28 +1536,43 @@ public class FeatureSettings extends JPanel int w, int h) { boolean thr = false; - String tt = ""; - String tx = ""; + StringBuilder tt = new StringBuilder(); + StringBuilder tx = new StringBuilder(); + + if (gcol.isColourByAttribute()) + { + tx.append(String.join(":", gcol.getAttributeName())); + } + else if (!gcol.isColourByLabel()) + { + tx.append(MessageManager.getString("label.score")); + } + tx.append(" "); if (gcol.isAboveThreshold()) { thr = true; - tx += ">"; - tt += "Thresholded (Above " + gcol.getThreshold() + ") "; + tx.append(">"); + tt.append("Thresholded (Above ").append(gcol.getThreshold()) + .append(") "); } if (gcol.isBelowThreshold()) { thr = true; - tx += "<"; - tt += "Thresholded (Below " + gcol.getThreshold() + ") "; + tx.append("<"); + tt.append("Thresholded (Below ").append(gcol.getThreshold()) + .append(") "); } if (gcol.isColourByLabel()) { - tt = "Coloured by label text. " + tt; + tt.append("Coloured by label text. ").append(tt); if (thr) { - tx += " "; + tx.append(" "); + } + if (!gcol.isColourByAttribute()) + { + tx.append("Label"); } - tx += "Label"; comp.setIcon(null); } else @@ -1670,19 +1588,261 @@ public class FeatureSettings extends JPanel // + ", " + minCol.getBlue() + ")"); } comp.setHorizontalAlignment(SwingConstants.CENTER); - comp.setText(tx); + comp.setText(tx.toString()); if (tt.length() > 0) { if (comp.getToolTipText() == null) { - comp.setToolTipText(tt); + comp.setToolTipText(tt.toString()); } else { - comp.setToolTipText(tt + " " + comp.getToolTipText()); + comp.setToolTipText( + tt.append(" ").append(comp.getToolTipText()).toString()); } } } + + class ColorEditor extends AbstractCellEditor + implements TableCellEditor, ActionListener + { + FeatureSettings me; + + FeatureColourI currentColor; + + FeatureTypeSettings chooser; + + String type; + + JButton button; + + JColorChooser colorChooser; + + JDialog dialog; + + protected static final String EDIT = "edit"; + + int rowSelected = 0; + + public ColorEditor(FeatureSettings me) + { + this.me = me; + // Set up the editor (from the table's point of view), + // which is a button. + // This button brings up the color chooser dialog, + // which is the editor from the user's point of view. + button = new JButton(); + button.setActionCommand(EDIT); + button.addActionListener(this); + button.setBorderPainted(false); + // Set up the dialog that the button brings up. + colorChooser = new JColorChooser(); + dialog = JColorChooser.createDialog(button, + MessageManager.getString("label.select_colour"), true, // modal + colorChooser, this, // OK button handler + null); // no CANCEL button handler + } + + /** + * Handles events from the editor button and from the dialog's OK button. + */ + @Override + public void actionPerformed(ActionEvent e) + { + // todo test e.getSource() instead here + if (EDIT.equals(e.getActionCommand())) + { + // The user has clicked the cell, so + // bring up the dialog. + if (currentColor.isSimpleColour()) + { + // bring up simple color chooser + button.setBackground(currentColor.getColour()); + colorChooser.setColor(currentColor.getColour()); + dialog.setVisible(true); + } + else + { + // bring up graduated chooser. + chooser = new FeatureTypeSettings(me.fr, type); + /** + * @j2sNative + */ + { + chooser.setRequestFocusEnabled(true); + chooser.requestFocus(); + } + chooser.addActionListener(this); + // Make the renderer reappear. + fireEditingStopped(); + } + } + else + { + if (currentColor.isSimpleColour()) + { + /* + * read off colour picked in colour chooser after OK pressed + */ + currentColor = new FeatureColour(colorChooser.getColor()); + me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN); + } + else + { + /* + * after OK in variable colour dialog, any changes to colour + * (or filters!) are already set in FeatureRenderer, so just + * update table data without triggering updateFeatureRenderer + */ + currentColor = fr.getFeatureColours().get(type); + FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type); + if (currentFilter == null) + { + currentFilter = new FeatureMatcherSet(); + } + Object[] data = ((FeatureTableModel) table.getModel()) + .getData()[rowSelected]; + data[COLOUR_COLUMN] = currentColor; + data[FILTER_COLUMN] = currentFilter; + } + fireEditingStopped(); + me.table.validate(); + } + } + + // Implement the one CellEditor method that AbstractCellEditor doesn't. + @Override + public Object getCellEditorValue() + { + return currentColor; + } + + // Implement the one method defined by TableCellEditor. + @Override + public Component getTableCellEditorComponent(JTable theTable, Object value, + boolean isSelected, int row, int column) + { + currentColor = (FeatureColourI) value; + this.rowSelected = row; + type = me.table.getValueAt(row, TYPE_COLUMN).toString(); + button.setOpaque(true); + button.setBackground(me.getBackground()); + if (!currentColor.isSimpleColour()) + { + JLabel btn = new JLabel(); + btn.setSize(button.getSize()); + FeatureSettings.renderGraduatedColor(btn, currentColor); + button.setBackground(btn.getBackground()); + button.setIcon(btn.getIcon()); + button.setText(btn.getText()); + } + else + { + button.setText(""); + button.setIcon(null); + button.setBackground(currentColor.getColour()); + } + return button; + } + } + + /** + * The cell editor for the Filter column. It displays the text of any filters + * for the feature type in that row (in full as a tooltip, possible abbreviated + * as display text). On click in the cell, opens the Feature Display Settings + * dialog at the Filters tab. + */ + class FilterEditor extends AbstractCellEditor + implements TableCellEditor, ActionListener + { + FeatureSettings me; + + FeatureMatcherSetI currentFilter; + + Point lastLocation; + + String type; + + JButton button; + + protected static final String EDIT = "edit"; + + int rowSelected = 0; + + public FilterEditor(FeatureSettings me) + { + this.me = me; + button = new JButton(); + button.setActionCommand(EDIT); + button.addActionListener(this); + button.setBorderPainted(false); + } + + /** + * Handles events from the editor button + */ + @Override + public void actionPerformed(ActionEvent e) + { + if (button == e.getSource()) + { + FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type); + chooser.addActionListener(this); + chooser.setRequestFocusEnabled(true); + chooser.requestFocus(); + if (lastLocation != null) + { + // todo open at its last position on screen + chooser.setBounds(lastLocation.x, lastLocation.y, + chooser.getWidth(), chooser.getHeight()); + chooser.validate(); + } + fireEditingStopped(); + } + else if (e.getSource() instanceof Component) + { + + /* + * after OK in variable colour dialog, any changes to filter + * (or colours!) are already set in FeatureRenderer, so just + * update table data without triggering updateFeatureRenderer + */ + FeatureColourI currentColor = fr.getFeatureColours().get(type); + currentFilter = me.fr.getFeatureFilter(type); + if (currentFilter == null) + { + currentFilter = new FeatureMatcherSet(); + } + Object[] data = ((FeatureTableModel) table.getModel()) + .getData()[rowSelected]; + data[COLOUR_COLUMN] = currentColor; + data[FILTER_COLUMN] = currentFilter; + fireEditingStopped(); + me.table.validate(); + } + } + + @Override + public Object getCellEditorValue() + { + return currentFilter; + } + + @Override + public Component getTableCellEditorComponent(JTable theTable, Object value, + boolean isSelected, int row, int column) + { + currentFilter = (FeatureMatcherSetI) value; + this.rowSelected = row; + type = me.table.getValueAt(row, TYPE_COLUMN).toString(); + button.setOpaque(true); + button.setBackground(me.getBackground()); + button.setText(currentFilter.toString()); + button.setToolTipText(currentFilter.toString()); + button.setIcon(null); + return button; + } + } } class FeatureIcon implements Icon @@ -1766,124 +1926,3 @@ class FeatureIcon implements Icon } } } - -class ColorEditor extends AbstractCellEditor - implements TableCellEditor, ActionListener -{ - FeatureSettings me; - - FeatureColourI currentColor; - - FeatureColourChooser chooser; - - String type; - - JButton button; - - JColorChooser colorChooser; - - JDialog dialog; - - protected static final String EDIT = "edit"; - - int selectedRow = 0; - - public ColorEditor(FeatureSettings me) - { - this.me = me; - // Set up the editor (from the table's point of view), - // which is a button. - // This button brings up the color chooser dialog, - // which is the editor from the user's point of view. - button = new JButton(); - button.setActionCommand(EDIT); - button.addActionListener(this); - button.setBorderPainted(false); - // Set up the dialog that the button brings up. - colorChooser = new JColorChooser(); - dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal - colorChooser, this, // OK button handler - null); // no CANCEL button handler - } - - /** - * Handles events from the editor button and from the dialog's OK button. - */ - @Override - public void actionPerformed(ActionEvent e) - { - - if (EDIT.equals(e.getActionCommand())) - { - // The user has clicked the cell, so - // bring up the dialog. - if (currentColor.isSimpleColour()) - { - // bring up simple color chooser - button.setBackground(currentColor.getColour()); - colorChooser.setColor(currentColor.getColour()); - dialog.setVisible(true); - } - else - { - // bring up graduated chooser. - chooser = new FeatureColourChooser(me.fr, type); - chooser.setRequestFocusEnabled(true); - chooser.requestFocus(); - chooser.addActionListener(this); - } - // Make the renderer reappear. - fireEditingStopped(); - - } - else - { // User pressed dialog's "OK" button. - if (currentColor.isSimpleColour()) - { - currentColor = new FeatureColour(colorChooser.getColor()); - } - else - { - currentColor = chooser.getLastColour(); - } - me.table.setValueAt(getCellEditorValue(), selectedRow, 1); - fireEditingStopped(); - me.table.validate(); - } - } - - // Implement the one CellEditor method that AbstractCellEditor doesn't. - @Override - public Object getCellEditorValue() - { - return currentColor; - } - - // Implement the one method defined by TableCellEditor. - @Override - public Component getTableCellEditorComponent(JTable table, Object value, - boolean isSelected, int row, int column) - { - currentColor = (FeatureColourI) value; - this.selectedRow = row; - type = me.table.getValueAt(row, 0).toString(); - button.setOpaque(true); - button.setBackground(me.getBackground()); - if (!currentColor.isSimpleColour()) - { - JLabel btn = new JLabel(); - btn.setSize(button.getSize()); - FeatureSettings.renderGraduatedColor(btn, currentColor); - button.setBackground(btn.getBackground()); - button.setIcon(btn.getIcon()); - button.setText(btn.getText()); - } - else - { - button.setText(""); - button.setIcon(null); - button.setBackground(currentColor.getColour()); - } - return button; - } -} diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java new file mode 100644 index 0000000..55bc519 --- /dev/null +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -0,0 +1,1743 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.gui; + +import jalview.api.AlignmentViewPanel; +import jalview.api.FeatureColourI; +import jalview.datamodel.GraphLine; +import jalview.datamodel.features.FeatureAttributes; +import jalview.datamodel.features.FeatureAttributes.Datatype; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; +import jalview.schemes.FeatureColour; +import jalview.util.ColorUtils; +import jalview.util.MessageManager; +import jalview.util.matcher.Condition; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JSlider; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.border.LineBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicArrowButton; + +/** + * A dialog where the user can configure colour scheme, and any filters, for one + * feature type + *

            + * (Was FeatureColourChooser prior to Jalview 1.11, renamed with the addition of + * filter options) + */ +public class FeatureTypeSettings extends JalviewDialog +{ + private final static String LABEL_18N = MessageManager + .getString("label.label"); + + private final static String SCORE_18N = MessageManager + .getString("label.score"); + + private static final int RADIO_WIDTH = 130; + + private static final String COLON = ":"; + + private static final int MAX_TOOLTIP_LENGTH = 50; + + private static final int NO_COLOUR_OPTION = 0; + + private static final int MIN_COLOUR_OPTION = 1; + + private static final int MAX_COLOUR_OPTION = 2; + + private static final int ABOVE_THRESHOLD_OPTION = 1; + + private static final int BELOW_THRESHOLD_OPTION = 2; + + private static final DecimalFormat DECFMT_2_2 = new DecimalFormat( + "##.##"); + + /* + * FeatureRenderer holds colour scheme and filters for feature types + */ + private final FeatureRenderer fr; // todo refactor to allow interface type + // here + + /* + * the view panel to update when settings change + */ + private final AlignmentViewPanel ap; + + private final String featureType; + + /* + * the colour and filters to reset to on Cancel + */ + private final FeatureColourI originalColour; + + private final FeatureMatcherSetI originalFilter; + + /* + * set flag to true when setting values programmatically, + * to avoid invocation of action handlers + */ + private boolean adjusting = false; + + /* + * minimum of the value range for graduated colour + * (may be for feature score or for a numeric attribute) + */ + private float min; + + /* + * maximum of the value range for graduated colour + */ + private float max; + + /* + * scale factor for conversion between absolute min-max and slider + */ + private float scaleFactor; + + /* + * radio button group, to select what to colour by: + * simple colour, by category (text), or graduated + */ + private JRadioButton simpleColour = new JRadioButton(); + + private JRadioButton byCategory = new JRadioButton(); + + private JRadioButton graduatedColour = new JRadioButton(); + + /** + * colours and filters are shown in tabbed view or single content pane + */ + JPanel coloursPanel, filtersPanel; + + JPanel singleColour = new JPanel(); + + private JPanel minColour = new JPanel(); + + private JPanel maxColour = new JPanel(); + + private JComboBox threshold = new JComboBox<>(); + + private JSlider slider = new JSlider(); + + private JTextField thresholdValue = new JTextField(20); + + private JCheckBox thresholdIsMin = new JCheckBox(); + + private GraphLine threshline; + + private ActionListener featureSettings = null; + + private ActionListener changeColourAction; + + /* + * choice of option for 'colour for no value' + */ + private JComboBox noValueCombo; + + /* + * choice of what to colour by text (Label or attribute) + */ + private JComboBox colourByTextCombo; + + /* + * choice of what to colour by range (Score or attribute) + */ + private JComboBox colourByRangeCombo; + + private JRadioButton andFilters; + + private JRadioButton orFilters; + + /* + * filters for the currently selected feature type + */ + private List filters; + + private JPanel chooseFiltersPanel; + + /** + * Constructor + * + * @param frender + * @param theType + */ + public FeatureTypeSettings(FeatureRenderer frender, String theType) + { + this.fr = frender; + this.featureType = theType; + ap = fr.ap; + originalFilter = fr.getFeatureFilter(theType); + originalColour = fr.getFeatureColours().get(theType); + + adjusting = true; + + try + { + initialise(); + } catch (Exception ex) + { + ex.printStackTrace(); + return; + } + + updateColoursTab(); + + updateFiltersTab(); + + adjusting = false; + + colourChanged(false); + + String title = MessageManager + .formatMessage("label.display_settings_for", new String[] + { theType }); + initDialogFrame(this, true, false, title, 500, 500); + + waitForInput(); + } + + /** + * Configures the widgets on the Colours tab according to the current feature + * colour scheme + */ + private void updateColoursTab() + { + FeatureColourI fc = fr.getFeatureColours().get(featureType); + + /* + * suppress action handling while updating values programmatically + */ + adjusting = true; + try + { + /* + * single colour + */ + if (fc.isSimpleColour()) + { + singleColour.setBackground(fc.getColour()); + singleColour.setForeground(fc.getColour()); + simpleColour.setSelected(true); + } + + /* + * colour by text (Label or attribute text) + */ + if (fc.isColourByLabel()) + { + byCategory.setSelected(true); + colourByTextCombo.setEnabled(colourByTextCombo.getItemCount() > 1); + if (fc.isColourByAttribute()) + { + String[] attributeName = fc.getAttributeName(); + colourByTextCombo.setSelectedItem( + FeatureMatcher.toAttributeDisplayName(attributeName)); + } + else + { + colourByTextCombo.setSelectedItem(LABEL_18N); + } + } + else + { + colourByTextCombo.setEnabled(false); + } + + if (!fc.isGraduatedColour()) + { + colourByRangeCombo.setEnabled(false); + minColour.setEnabled(false); + maxColour.setEnabled(false); + noValueCombo.setEnabled(false); + threshold.setEnabled(false); + slider.setEnabled(false); + thresholdValue.setEnabled(false); + thresholdIsMin.setEnabled(false); + return; + } + + /* + * Graduated colour, by score or attribute value range + */ + graduatedColour.setSelected(true); + updateColourMinMax(); // ensure min, max are set + colourByRangeCombo.setEnabled(colourByRangeCombo.getItemCount() > 1); + minColour.setEnabled(true); + maxColour.setEnabled(true); + noValueCombo.setEnabled(true); + threshold.setEnabled(true); + minColour.setBackground(fc.getMinColour()); + maxColour.setBackground(fc.getMaxColour()); + + if (fc.isColourByAttribute()) + { + String[] attributeName = fc.getAttributeName(); + colourByRangeCombo.setSelectedItem( + FeatureMatcher.toAttributeDisplayName(attributeName)); + } + else + { + colourByRangeCombo.setSelectedItem(SCORE_18N); + } + Color noColour = fc.getNoColour(); + if (noColour == null) + { + noValueCombo.setSelectedIndex(NO_COLOUR_OPTION); + } + else if (noColour.equals(fc.getMinColour())) + { + noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION); + } + else if (noColour.equals(fc.getMaxColour())) + { + noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION); + } + + /* + * update min-max scaling if there is a range to work with, + * else disable the widgets (this shouldn't happen if only + * valid options are offered in the combo box) + */ + scaleFactor = (max == min) ? 1f : 100f / (max - min); + float range = (max - min) * scaleFactor; + slider.setMinimum((int) (min * scaleFactor)); + slider.setMaximum((int) (max * scaleFactor)); + slider.setMajorTickSpacing((int) (range / 10f)); + + threshline = new GraphLine((max - min) / 2f, "Threshold", + Color.black); + threshline.value = fc.getThreshold(); + + if (fc.hasThreshold()) + { + threshold.setSelectedIndex( + fc.isAboveThreshold() ? ABOVE_THRESHOLD_OPTION + : BELOW_THRESHOLD_OPTION); + slider.setEnabled(true); + slider.setValue((int) (fc.getThreshold() * scaleFactor)); + thresholdValue.setText(String.valueOf(getRoundedSliderValue())); + thresholdValue.setEnabled(true); + thresholdIsMin.setEnabled(true); + } + else + { + slider.setEnabled(false); + thresholdValue.setEnabled(false); + thresholdIsMin.setEnabled(false); + } + thresholdIsMin.setSelected(!fc.isAutoScaled()); + } finally + { + adjusting = false; + } + } + + /** + * Configures the initial layout + */ + private void initialise() + { + this.setLayout(new BorderLayout()); + + /* + * an ActionListener that applies colour changes + */ + changeColourAction = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + colourChanged(true); + } + }; + + /* + * first panel/tab: colour options + */ + JPanel coloursPanel = initialiseColoursPanel(); + this.add(coloursPanel, BorderLayout.NORTH); + + /* + * second panel/tab: filter options + */ + JPanel filtersPanel = initialiseFiltersPanel(); + this.add(filtersPanel, BorderLayout.CENTER); + + JPanel okCancelPanel = initialiseOkCancelPanel(); + + this.add(okCancelPanel, BorderLayout.SOUTH); + } + + /** + * Updates the min-max range if Colour By selected item is Score, or an + * attribute, with a min-max range + */ + protected void updateColourMinMax() + { + if (!graduatedColour.isSelected()) + { + return; + } + + String colourBy = (String) colourByRangeCombo.getSelectedItem(); + float[] minMax = getMinMax(colourBy); + + if (minMax != null) + { + min = minMax[0]; + max = minMax[1]; + } + } + + /** + * Retrieves the min-max range: + *

              + *
            • of feature score, if colour or filter is by Score
            • + *
            • else of the selected attribute
            • + *
            + * + * @param attName + * @return + */ + private float[] getMinMax(String attName) + { + float[] minMax = null; + if (SCORE_18N.equals(attName)) + { + minMax = fr.getMinMax().get(featureType)[0]; + } + else + { + // colour by attribute range + minMax = FeatureAttributes.getInstance().getMinMax(featureType, + FeatureMatcher.fromAttributeDisplayName(attName)); + } + return minMax; + } + + /** + * Lay out fields for graduated colour (by score or attribute value) + * + * @return + */ + private JPanel initialiseGraduatedColourPanel() + { + JPanel graduatedColourPanel = new JPanel(); + graduatedColourPanel.setLayout( + new BoxLayout(graduatedColourPanel, BoxLayout.Y_AXIS)); + JvSwingUtils.createTitledBorder(graduatedColourPanel, + MessageManager.getString("label.graduated_colour"), true); + graduatedColourPanel.setBackground(Color.white); + + /* + * first row: graduated colour radio button, score/attribute drop-down + */ + JPanel graduatedChoicePanel = new JPanel( + new FlowLayout(FlowLayout.LEFT)); + graduatedChoicePanel.setBackground(Color.white); + graduatedColour = new JRadioButton( + MessageManager.getString("label.by_range_of") + COLON); + graduatedColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + graduatedColour.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (graduatedColour.isSelected()) + { + colourChanged(true); + } + } + }); + graduatedChoicePanel.add(graduatedColour); + + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + colourByRangeCombo = populateAttributesDropdown(attNames, true, false); + colourByRangeCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + + /* + * disable graduated colour option if no range found + */ + graduatedColour.setEnabled(colourByRangeCombo.getItemCount() > 0); + + graduatedChoicePanel.add(colourByRangeCombo); + graduatedColourPanel.add(graduatedChoicePanel); + + /* + * second row - min/max/no colours + */ + JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + colourRangePanel.setBackground(Color.white); + graduatedColourPanel.add(colourRangePanel); + + minColour.setFont(JvSwingUtils.getLabelFont()); + minColour.setBorder(BorderFactory.createLineBorder(Color.black)); + minColour.setPreferredSize(new Dimension(40, 20)); + minColour.setToolTipText(MessageManager.getString("label.min_colour")); + minColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + if (minColour.isEnabled()) + { + showColourChooser(minColour, "label.select_colour_minimum_value"); + } + } + }); + + maxColour.setFont(JvSwingUtils.getLabelFont()); + maxColour.setBorder(BorderFactory.createLineBorder(Color.black)); + maxColour.setPreferredSize(new Dimension(40, 20)); + maxColour.setToolTipText(MessageManager.getString("label.max_colour")); + maxColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + if (maxColour.isEnabled()) + { + showColourChooser(maxColour, "label.select_colour_maximum_value"); + } + } + }); + maxColour.setBorder(new LineBorder(Color.black)); + + /* + * default max colour to last plain colour; + * make min colour a pale version of max colour + */ + FeatureColourI fc = fr.getFeatureColours().get(featureType); + Color bg = fc.getColour() == null ? Color.BLACK : fc.getColour(); + maxColour.setBackground(bg); + minColour.setBackground(ColorUtils.bleachColour(bg, 0.9f)); + + noValueCombo = new JComboBox<>(); + noValueCombo.addItem(MessageManager.getString("label.no_colour")); + noValueCombo.addItem(MessageManager.getString("label.min_colour")); + noValueCombo.addItem(MessageManager.getString("label.max_colour")); + noValueCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + + JLabel minText = new JLabel( + MessageManager.getString("label.min_value") + COLON); + minText.setFont(JvSwingUtils.getLabelFont()); + JLabel maxText = new JLabel( + MessageManager.getString("label.max_value") + COLON); + maxText.setFont(JvSwingUtils.getLabelFont()); + JLabel noText = new JLabel( + MessageManager.getString("label.no_value") + COLON); + noText.setFont(JvSwingUtils.getLabelFont()); + + colourRangePanel.add(minText); + colourRangePanel.add(minColour); + colourRangePanel.add(maxText); + colourRangePanel.add(maxColour); + colourRangePanel.add(noText); + colourRangePanel.add(noValueCombo); + + /* + * third row - threshold options and value + */ + JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + thresholdPanel.setBackground(Color.white); + graduatedColourPanel.add(thresholdPanel); + + threshold.addActionListener(changeColourAction); + threshold.setToolTipText(MessageManager + .getString("label.threshold_feature_display_by_score")); + threshold.addItem(MessageManager + .getString("label.threshold_feature_no_threshold")); // index 0 + threshold.addItem(MessageManager + .getString("label.threshold_feature_above_threshold")); // index 1 + threshold.addItem(MessageManager + .getString("label.threshold_feature_below_threshold")); // index 2 + + thresholdValue.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + thresholdValue_actionPerformed(); + } + }); + thresholdValue.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + thresholdValue_actionPerformed(); + } + }); + slider.setPaintLabels(false); + slider.setPaintTicks(true); + slider.setBackground(Color.white); + slider.setEnabled(false); + slider.setOpaque(false); + slider.setPreferredSize(new Dimension(100, 32)); + slider.setToolTipText( + MessageManager.getString("label.adjust_threshold")); + + slider.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent evt) + { + if (!adjusting) + { + thresholdValue + .setText(String.valueOf(slider.getValue() / scaleFactor)); + sliderValueChanged(); + } + } + }); + slider.addMouseListener(new MouseAdapter() + { + @Override + public void mouseReleased(MouseEvent evt) + { + /* + * only update Overview and/or structure colouring + * when threshold slider drag ends (mouse up) + */ + if (ap != null) + { + ap.paintAlignment(true, true); + } + } + }); + + thresholdValue.setEnabled(false); + thresholdValue.setColumns(7); + + thresholdPanel.add(threshold); + thresholdPanel.add(slider); + thresholdPanel.add(thresholdValue); + + thresholdIsMin.setBackground(Color.white); + thresholdIsMin + .setText(MessageManager.getString("label.threshold_minmax")); + thresholdIsMin.setToolTipText(MessageManager + .getString("label.toggle_absolute_relative_display_threshold")); + thresholdIsMin.addActionListener(changeColourAction); + thresholdPanel.add(thresholdIsMin); + + return graduatedColourPanel; + } + + /** + * Lay out OK and Cancel buttons + * + * @return + */ + private JPanel initialiseOkCancelPanel() + { + JPanel okCancelPanel = new JPanel(); + // okCancelPanel.setBackground(Color.white); + okCancelPanel.add(ok); + okCancelPanel.add(cancel); + return okCancelPanel; + } + + /** + * Lay out Colour options panel, containing + *
              + *
            • plain colour, with colour picker
            • + *
            • colour by text, with choice of Label or other attribute
            • + *
            • colour by range, of score or other attribute, when available
            • + *
            + * + * @return + */ + private JPanel initialiseColoursPanel() + { + JPanel colourByPanel = new JPanel(); + colourByPanel.setBackground(Color.white); + colourByPanel.setLayout(new BoxLayout(colourByPanel, BoxLayout.Y_AXIS)); + JvSwingUtils.createTitledBorder(colourByPanel, + MessageManager.getString("action.colour"), true); + + /* + * simple colour radio button and colour picker + */ + JPanel simpleColourPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + simpleColourPanel.setBackground(Color.white); + colourByPanel.add(simpleColourPanel); + + simpleColour = new JRadioButton( + MessageManager.getString("label.simple_colour")); + simpleColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + simpleColour.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (simpleColour.isSelected() && !adjusting) + { + colourChanged(true); + } + } + }); + + singleColour.setFont(JvSwingUtils.getLabelFont()); + singleColour.setBorder(BorderFactory.createLineBorder(Color.black)); + singleColour.setPreferredSize(new Dimension(40, 20)); + if (originalColour.isGraduatedColour()) + { + singleColour.setBackground(originalColour.getMaxColour()); + singleColour.setForeground(originalColour.getMaxColour()); + } + else + { + singleColour.setBackground(originalColour.getColour()); + singleColour.setForeground(originalColour.getColour()); + } + singleColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + if (simpleColour.isSelected()) + { + showColourChooser(singleColour, "label.select_colour"); + } + } + }); + simpleColourPanel.add(simpleColour); // radio button + simpleColourPanel.add(singleColour); // colour picker button + + /* + * colour by text (category) radio button and drop-down choice list + */ + JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + byTextPanel.setBackground(Color.white); + JvSwingUtils.createTitledBorder(byTextPanel, + MessageManager.getString("label.colour_by_text"), true); + colourByPanel.add(byTextPanel); + byCategory = new JRadioButton( + MessageManager.getString("label.by_text_of") + COLON); + byCategory.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + byCategory.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (byCategory.isSelected()) + { + colourChanged(true); + } + } + }); + byTextPanel.add(byCategory); + + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + colourByTextCombo = populateAttributesDropdown(attNames, false, true); + colourByTextCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + byTextPanel.add(colourByTextCombo); + + /* + * graduated colour panel + */ + JPanel graduatedColourPanel = initialiseGraduatedColourPanel(); + colourByPanel.add(graduatedColourPanel); + + /* + * 3 radio buttons select between simple colour, + * by category (text), or graduated + */ + ButtonGroup bg = new ButtonGroup(); + bg.add(simpleColour); + bg.add(byCategory); + bg.add(graduatedColour); + + return colourByPanel; + } + + private void showColourChooser(JPanel colourPanel, String key) + { + Color col = JColorChooser.showDialog(this, + MessageManager.getString(key), colourPanel.getBackground()); + if (col != null) + { + colourPanel.setBackground(col); + colourPanel.setForeground(col); + } + colourPanel.repaint(); + colourChanged(true); + } + + /** + * Constructs and sets the selected colour options as the colour for the + * feature type, and repaints the alignment, and optionally the Overview + * and/or structure viewer if open + * + * @param updateStructsAndOverview + */ + void colourChanged(boolean updateStructsAndOverview) + { + if (adjusting) + { + /* + * ignore action handlers while setting values programmatically + */ + return; + } + + /* + * ensure min-max range is for the latest choice of + * 'graduated colour by' + */ + updateColourMinMax(); + + FeatureColourI acg = makeColourFromInputs(); + + /* + * save the colour, and repaint stuff + */ + fr.setColour(featureType, acg); + ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); + + updateColoursTab(); + } + + /** + * Converts the input values into an instance of FeatureColour + * + * @return + */ + private FeatureColourI makeColourFromInputs() + { + /* + * easiest case - a single colour + */ + if (simpleColour.isSelected()) + { + return new FeatureColour(singleColour.getBackground()); + } + + /* + * next easiest case - colour by Label, or attribute text + */ + if (byCategory.isSelected()) + { + Color c = singleColour.getBackground(); + FeatureColourI fc = new FeatureColour(c); + fc.setColourByLabel(true); + String byWhat = (String) colourByTextCombo.getSelectedItem(); + if (!LABEL_18N.equals(byWhat)) + { + fc.setAttributeName( + FeatureMatcher.fromAttributeDisplayName(byWhat)); + } + return fc; + } + + /* + * remaining case - graduated colour by score, or attribute value + */ + Color noColour = null; + if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION) + { + noColour = minColour.getBackground(); + } + else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION) + { + noColour = maxColour.getBackground(); + } + + float thresh = 0f; + try + { + thresh = Float.valueOf(thresholdValue.getText()); + } catch (NumberFormatException e) + { + // invalid inputs are already handled on entry + } + + /* + * min-max range is to (or from) threshold value if + * 'threshold is min/max' is selected + */ + float minValue = min; + float maxValue = max; + final int thresholdOption = threshold.getSelectedIndex(); + if (thresholdIsMin.isSelected() + && thresholdOption == ABOVE_THRESHOLD_OPTION) + { + minValue = thresh; + } + if (thresholdIsMin.isSelected() + && thresholdOption == BELOW_THRESHOLD_OPTION) + { + maxValue = thresh; + } + + /* + * make the graduated colour + */ + FeatureColourI fc = new FeatureColour(minColour.getBackground(), + maxColour.getBackground(), noColour, minValue, maxValue); + + /* + * set attribute to colour by if selected + */ + String byWhat = (String) colourByRangeCombo.getSelectedItem(); + if (!SCORE_18N.equals(byWhat)) + { + fc.setAttributeName(FeatureMatcher.fromAttributeDisplayName(byWhat)); + } + + /* + * set threshold options and 'autoscaled' which is + * false if 'threshold is min/max' is selected + * else true (colour range is on actual range of values) + */ + fc.setThreshold(thresh); + fc.setAutoScaled(!thresholdIsMin.isSelected()); + fc.setAboveThreshold(thresholdOption == ABOVE_THRESHOLD_OPTION); + fc.setBelowThreshold(thresholdOption == BELOW_THRESHOLD_OPTION); + + if (threshline == null) + { + /* + * todo not yet implemented: visual indication of feature threshold + */ + threshline = new GraphLine((max - min) / 2f, "Threshold", + Color.black); + } + + return fc; + } + + @Override + protected void raiseClosed() + { + if (this.featureSettings != null) + { + featureSettings.actionPerformed(new ActionEvent(this, 0, "CLOSED")); + } + } + + /** + * Action on OK is just to dismiss the dialog - any changes have already been + * applied + */ + @Override + public void okPressed() + { + } + + /** + * Action on Cancel is to restore colour scheme and filters as they were when + * the dialog was opened + */ + @Override + public void cancelPressed() + { + fr.setColour(featureType, originalColour); + fr.setFeatureFilter(featureType, originalFilter); + ap.paintAlignment(true, true); + } + + /** + * Action on text entry of a threshold value + */ + protected void thresholdValue_actionPerformed() + { + try + { + adjusting = true; + float f = Float.parseFloat(thresholdValue.getText()); + slider.setValue((int) (f * scaleFactor)); + threshline.value = f; + thresholdValue.setBackground(Color.white); // ok + + /* + * force repaint of any Overview window or structure + */ + ap.paintAlignment(true, true); + } catch (NumberFormatException ex) + { + thresholdValue.setBackground(Color.red); // not ok + } finally + { + adjusting = false; + } + } + + /** + * Action on change of threshold slider value. This may be done interactively + * (by moving the slider), or programmatically (to update the slider after + * manual input of a threshold value). + */ + protected void sliderValueChanged() + { + threshline.value = getRoundedSliderValue(); + + /* + * repaint alignment, but not Overview or structure, + * to avoid overload while dragging the slider + */ + colourChanged(false); + } + + /** + * Converts the slider value to its absolute value by dividing by the + * scaleFactor. Rounding errors are squashed by forcing min/max of slider + * range to the actual min/max of feature score range + * + * @return + */ + private float getRoundedSliderValue() + { + int value = slider.getValue(); + float f = value == slider.getMaximum() ? max + : (value == slider.getMinimum() ? min : value / scaleFactor); + return f; + } + + void addActionListener(ActionListener listener) + { + if (featureSettings != null) + { + System.err.println( + "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); + } + featureSettings = listener; + } + + /** + * A helper method to build the drop-down choice of attributes for a feature. + * If 'withRange' is true, then Score, and any attributes with a min-max + * range, are added. If 'withText' is true, Label and any known attributes are + * added. This allows 'categorical numerical' attributes e.g. codon position + * to be coloured by text. + *

            + * Where metadata is available with a description for an attribute, that is + * added as a tooltip. + *

            + * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ", + * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele. + *

            + * This method does not add any ActionListener to the JComboBox. + * + * @param attNames + * @param withRange + * @param withText + */ + protected JComboBox populateAttributesDropdown( + List attNames, boolean withRange, boolean withText) + { + List displayAtts = new ArrayList<>(); + List tooltips = new ArrayList<>(); + + if (withText) + { + displayAtts.add(LABEL_18N); + tooltips.add(MessageManager.getString("label.description")); + } + if (withRange) + { + float[][] minMax = fr.getMinMax().get(featureType); + if (minMax != null && minMax[0][0] != minMax[0][1]) + { + displayAtts.add(SCORE_18N); + tooltips.add(SCORE_18N); + } + } + + FeatureAttributes fa = FeatureAttributes.getInstance(); + for (String[] attName : attNames) + { + float[] minMax = fa.getMinMax(featureType, attName); + boolean hasRange = minMax != null && minMax[0] != minMax[1]; + if (!withText && !hasRange) + { + continue; + } + displayAtts.add(FeatureMatcher.toAttributeDisplayName(attName)); + String desc = fa.getDescription(featureType, attName); + if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) + { + desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "..."; + } + tooltips.add(desc == null ? "" : desc); + } + + JComboBox attCombo = JvSwingUtils + .buildComboWithTooltips(displayAtts, tooltips); + + return attCombo; + } + + /** + * Populates initial layout of the feature attribute filters panel + */ + private JPanel initialiseFiltersPanel() + { + filters = new ArrayList<>(); + + JPanel filtersPanel = new JPanel(); + filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS)); + filtersPanel.setBackground(Color.white); + JvSwingUtils.createTitledBorder(filtersPanel, + MessageManager.getString("label.filters"), true); + + JPanel andOrPanel = initialiseAndOrPanel(); + filtersPanel.add(andOrPanel); + + /* + * panel with filters - populated by refreshFiltersDisplay, + * which also sets the layout manager + */ + chooseFiltersPanel = new JPanel(); + chooseFiltersPanel.setBackground(Color.white); + filtersPanel.add(chooseFiltersPanel); + + return filtersPanel; + } + + /** + * Lays out the panel with radio buttons to AND or OR filter conditions + * + * @return + */ + private JPanel initialiseAndOrPanel() + { + JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + andOrPanel.setBackground(Color.white); + andFilters = new JRadioButton(MessageManager.getString("label.and")); + orFilters = new JRadioButton(MessageManager.getString("label.or")); + ActionListener actionListener = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + filtersChanged(); + } + }; + andFilters.addActionListener(actionListener); + orFilters.addActionListener(actionListener); + ButtonGroup andOr = new ButtonGroup(); + andOr.add(andFilters); + andOr.add(orFilters); + andFilters.setSelected(true); + andOrPanel.add( + new JLabel(MessageManager.getString("label.join_conditions"))); + andOrPanel.add(andFilters); + andOrPanel.add(orFilters); + return andOrPanel; + } + + /** + * Refreshes the display to show any filters currently configured for the + * selected feature type (editable, with 'remove' option), plus one extra row + * for adding a condition. This should be called after a filter has been + * removed, added or amended. + */ + private void updateFiltersTab() + { + /* + * clear the panel and list of filter conditions + */ + chooseFiltersPanel.removeAll(); + filters.clear(); + + /* + * look up attributes known for feature type + */ + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + + /* + * if this feature type has filters set, load them first + */ + FeatureMatcherSetI featureFilters = fr.getFeatureFilter(featureType); + if (featureFilters != null) + { + if (!featureFilters.isAnded()) + { + orFilters.setSelected(true); + } + featureFilters.getMatchers().forEach(matcher -> filters.add(matcher)); + } + + /* + * and an empty filter for the user to populate (add) + */ + filters.add(FeatureMatcher.NULL_MATCHER); + + /* + * use GridLayout to 'justify' rows to the top of the panel, until + * there are too many to fit in, then fall back on BoxLayout + */ + if (filters.size() <= 5) + { + chooseFiltersPanel.setLayout(new GridLayout(5, 1)); + } + else + { + chooseFiltersPanel.setLayout( + new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS)); + } + + /* + * render the conditions in rows, each in its own JPanel + */ + int filterIndex = 0; + for (FeatureMatcherI filter : filters) + { + JPanel row = addFilter(filter, attNames, filterIndex); + chooseFiltersPanel.add(row); + filterIndex++; + } + + this.validate(); + this.repaint(); + } + + /** + * A helper method that constructs a row (panel) with one filter condition: + *

              + *
            • a drop-down list of Label, Score and attribute names to choose + * from
            • + *
            • a drop-down list of conditions to choose from
            • + *
            • a text field for input of a match pattern
            • + *
            • optionally, a 'remove' button
            • + *
            + * The filter values are set as defaults for the input fields. The 'remove' + * button is added unless the pattern is empty (incomplete filter condition). + *

            + * Action handlers on these fields provide for + *

              + *
            • validate pattern field - should be numeric if condition is numeric
            • + *
            • save filters and refresh display on any (valid) change
            • + *
            • remove filter and refresh on 'Remove'
            • + *
            • update conditions list on change of Label/Score/Attribute
            • + *
            • refresh value field tooltip with min-max range on change of + * attribute
            • + *
            + * + * @param filter + * @param attNames + * @param filterIndex + * @return + */ + protected JPanel addFilter(FeatureMatcherI filter, + List attNames, int filterIndex) + { + String[] attName = filter.getAttribute(); + Condition cond = filter.getMatcher().getCondition(); + String pattern = filter.getMatcher().getPattern(); + + JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); + filterRow.setBackground(Color.white); + + /* + * drop-down choice of attribute, with description as a tooltip + * if we can obtain it + */ + final JComboBox attCombo = populateAttributesDropdown(attNames, + true, true); + String filterBy = setSelectedAttribute(attCombo, filter); + + JComboBox condCombo = new JComboBox<>(); + + JTextField patternField = new JTextField(8); + patternField.setText(pattern); + + /* + * action handlers that validate and (if valid) apply changes + */ + ActionListener actionListener = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + if (validateFilter(patternField, condCombo)) + { + if (updateFilter(attCombo, condCombo, patternField, filterIndex)) + { + filtersChanged(); + } + } + } + }; + ItemListener itemListener = new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + actionListener.actionPerformed(null); + } + }; + + if (filter == FeatureMatcher.NULL_MATCHER) // the 'add a condition' row + { + attCombo.setSelectedIndex(0); + } + else + { + attCombo.setSelectedItem( + FeatureMatcher.toAttributeDisplayName(attName)); + } + attCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + /* + * on change of attribute, refresh the conditions list to + * ensure it is appropriate for the attribute datatype + */ + populateConditions((String) attCombo.getSelectedItem(), + (Condition) condCombo.getSelectedItem(), condCombo, + patternField); + actionListener.actionPerformed(null); + } + }); + + filterRow.add(attCombo); + + /* + * drop-down choice of test condition + */ + populateConditions(filterBy, cond, condCombo, patternField); + condCombo.setPreferredSize(new Dimension(150, 20)); + condCombo.addItemListener(itemListener); + filterRow.add(condCombo); + + /* + * pattern to match against + */ + patternField.addActionListener(actionListener); + patternField.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + actionListener.actionPerformed(null); + } + }); + filterRow.add(patternField); + + /* + * disable pattern field for condition 'Present / NotPresent' + */ + Condition selectedCondition = (Condition) condCombo.getSelectedItem(); + patternField.setEnabled(selectedCondition.needsAPattern()); + + /* + * if a numeric condition is selected, show the value range + * as a tooltip on the value input field + */ + setNumericHints(filterBy, selectedCondition, patternField); + + /* + * add remove button if filter is populated (non-empty pattern) + */ + if (!patternField.isEnabled() + || (pattern != null && pattern.trim().length() > 0)) + { + // todo: gif for button drawing '-' or 'x' + JButton removeCondition = new BasicArrowButton(SwingConstants.WEST); + removeCondition + .setToolTipText(MessageManager.getString("label.delete_row")); + removeCondition.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + filters.remove(filterIndex); + filtersChanged(); + } + }); + filterRow.add(removeCondition); + } + + return filterRow; + } + + /** + * Sets the selected item in the Label/Score/Attribute drop-down to match the + * filter + * + * @param attCombo + * @param filter + */ + private String setSelectedAttribute(JComboBox attCombo, + FeatureMatcherI filter) + { + String item = null; + if (filter.isByScore()) + { + item = SCORE_18N; + } + else if (filter.isByLabel()) + { + item = LABEL_18N; + } + else + { + item = FeatureMatcher.toAttributeDisplayName(filter.getAttribute()); + } + attCombo.setSelectedItem(item); + return item; + } + + /** + * If a numeric comparison condition is selected, retrieves the min-max range + * for the value (score or attribute), and sets it as a tooltip on the value + * field. If the field is currently empty, then pre-populates it with + *
              + *
            • the minimum value, if condition is > or >=
            • + *
            • the maximum value, if condition is < or <=
            • + *
            + * + * @param attName + * @param selectedCondition + * @param patternField + */ + private void setNumericHints(String attName, Condition selectedCondition, + JTextField patternField) + { + patternField.setToolTipText(""); + + if (selectedCondition.isNumeric()) + { + float[] minMax = getMinMax(attName); + if (minMax != null) + { + String minFormatted = DECFMT_2_2.format(minMax[0]); + String maxFormatted = DECFMT_2_2.format(minMax[1]); + String tip = String.format("(%s - %s)", minFormatted, maxFormatted); + patternField.setToolTipText(tip); + if (patternField.getText().isEmpty()) + { + if (selectedCondition == Condition.GE + || selectedCondition == Condition.GT) + { + patternField.setText(minFormatted); + } + else + { + if (selectedCondition == Condition.LE + || selectedCondition == Condition.LT) + { + patternField.setText(maxFormatted); + } + } + } + } + } + } + + /** + * Populates the drop-down list of comparison conditions for the given + * attribute name. The conditions added depend on the datatype of the + * attribute values. The supplied condition is set as the selected item in the + * list, provided it is in the list. If the pattern is now invalid + * (non-numeric pattern for a numeric condition), it is cleared. + * + * @param attName + * @param cond + * @param condCombo + * @param patternField + */ + private void populateConditions(String attName, Condition cond, + JComboBox condCombo, JTextField patternField) + { + Datatype type = FeatureAttributes.getInstance().getDatatype(featureType, + FeatureMatcher.fromAttributeDisplayName(attName)); + if (LABEL_18N.equals(attName)) + { + type = Datatype.Character; + } + else if (SCORE_18N.equals(attName)) + { + type = Datatype.Number; + } + + /* + * remove itemListener before starting + */ + ItemListener listener = condCombo.getItemListeners()[0]; + condCombo.removeItemListener(listener); + boolean condIsValid = false; + + condCombo.removeAllItems(); + for (Condition c : Condition.values()) + { + if ((c.isNumeric() && type == Datatype.Number) + || (!c.isNumeric() && type != Datatype.Number)) + { + condCombo.addItem(c); + if (c == cond) + { + condIsValid = true; + } + } + } + + /* + * set the selected condition (does nothing if not in the list) + */ + if (condIsValid) + { + condCombo.setSelectedItem(cond); + } + else + { + condCombo.setSelectedIndex(0); + } + + /* + * clear pattern if it is now invalid for condition + */ + if (((Condition) condCombo.getSelectedItem()).isNumeric()) + { + try + { + String pattern = patternField.getText().trim(); + if (pattern.length() > 0) + { + Float.valueOf(pattern); + } + } catch (NumberFormatException e) + { + patternField.setText(""); + } + } + + /* + * restore the listener + */ + condCombo.addItemListener(listener); + } + + /** + * Answers true unless a numeric condition has been selected with a + * non-numeric value. Sets the value field to RED with a tooltip if in error. + *

            + * If the pattern is expected but is empty, this method returns false, but + * does not mark the field as invalid. This supports selecting an attribute + * for a new condition before a match pattern has been entered. + * + * @param value + * @param condCombo + */ + protected boolean validateFilter(JTextField value, + JComboBox condCombo) + { + if (value == null || condCombo == null) + { + return true; // fields not populated + } + + Condition cond = (Condition) condCombo.getSelectedItem(); + if (!cond.needsAPattern()) + { + return true; + } + + value.setBackground(Color.white); + value.setToolTipText(""); + String v1 = value.getText().trim(); + if (v1.length() == 0) + { + // return false; + } + + if (cond.isNumeric() && v1.length() > 0) + { + try + { + Float.valueOf(v1); + } catch (NumberFormatException e) + { + value.setBackground(Color.red); + value.setToolTipText( + MessageManager.getString("label.numeric_required")); + return false; + } + } + + return true; + } + + /** + * Constructs a filter condition from the given input fields, and replaces the + * condition at filterIndex with the new one. Does nothing if the pattern + * field is blank (unless the match condition is one that doesn't require a + * pattern, e.g. 'Is present'). Answers true if the filter was updated, else + * false. + *

            + * This method may update the tooltip on the filter value field to show the + * value range, if a numeric condition is selected. This ensures the tooltip + * is updated when a numeric valued attribute is chosen on the last 'add a + * filter' row. + * + * @param attCombo + * @param condCombo + * @param valueField + * @param filterIndex + */ + protected boolean updateFilter(JComboBox attCombo, + JComboBox condCombo, JTextField valueField, + int filterIndex) + { + String attName = (String) attCombo.getSelectedItem(); + Condition cond = (Condition) condCombo.getSelectedItem(); + String pattern = valueField.getText().trim(); + + setNumericHints(attName, cond, valueField); + + if (pattern.length() == 0 && cond.needsAPattern()) + { + valueField.setEnabled(true); // ensure pattern field is enabled! + return false; + } + + /* + * Construct a matcher that operates on Label, Score, + * or named attribute + */ + FeatureMatcherI km = null; + if (LABEL_18N.equals(attName)) + { + km = FeatureMatcher.byLabel(cond, pattern); + } + else if (SCORE_18N.equals(attName)) + { + km = FeatureMatcher.byScore(cond, pattern); + } + else + { + km = FeatureMatcher.byAttribute(cond, pattern, + FeatureMatcher.fromAttributeDisplayName(attName)); + } + + filters.set(filterIndex, km); + + return true; + } + + /** + * Action on any change to feature filtering, namely + *

              + *
            • change of selected attribute
            • + *
            • change of selected condition
            • + *
            • change of match pattern
            • + *
            • removal of a condition
            • + *
            + * The inputs are parsed into a combined filter and this is set for the + * feature type, and the alignment redrawn. + */ + protected void filtersChanged() + { + /* + * update the filter conditions for the feature type + */ + boolean anded = andFilters.isSelected(); + FeatureMatcherSetI combined = new FeatureMatcherSet(); + + for (FeatureMatcherI filter : filters) + { + String pattern = filter.getMatcher().getPattern(); + Condition condition = filter.getMatcher().getCondition(); + if (pattern.trim().length() > 0 || !condition.needsAPattern()) + { + if (anded) + { + combined.and(filter); + } + else + { + combined.or(filter); + } + } + } + + /* + * save the filter conditions in the FeatureRenderer + * (note this might now be an empty filter with no conditions) + */ + fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined); + ap.paintAlignment(true, true); + + updateFiltersTab(); + } +} diff --git a/src/jalview/gui/FontChooser.java b/src/jalview/gui/FontChooser.java index f3c8e8f..c66f304 100755 --- a/src/jalview/gui/FontChooser.java +++ b/src/jalview/gui/FontChooser.java @@ -134,7 +134,7 @@ public class FontChooser extends GFontChooser fontAsCdna.setSelected(ap.av.isProteinFontAsCdna()); } - if (tp != null) + if (isTreeFont()) { Desktop.addInternalFrame(frame, MessageManager.getString("action.change_font_tree_panel"), @@ -229,7 +229,11 @@ public class FontChooser extends GFontChooser @Override protected void cancel_actionPerformed() { - if (ap != null) + if (isTreeFont()) + { + tp.setTreeFont(oldFont); + } + else if (ap != null) { ap.av.setFont(oldFont, true); ap.av.setScaleProteinAsCdna(oldProteinScale); @@ -250,10 +254,6 @@ public class FontChooser extends GFontChooser splitFrame.repaint(); } } - else if (tp != null) - { - tp.setTreeFont(oldFont); - } try { @@ -263,6 +263,11 @@ public class FontChooser extends GFontChooser } } + private boolean isTreeFont() + { + return tp != null; + } + /** * DOCUMENT ME! */ @@ -317,7 +322,7 @@ public class FontChooser extends GFontChooser } return; } - if (tp != null) + if (isTreeFont()) { tp.setTreeFont(newFont); } diff --git a/src/jalview/gui/IdCanvas.java b/src/jalview/gui/IdCanvas.java index 085b259..cf88c90 100755 --- a/src/jalview/gui/IdCanvas.java +++ b/src/jalview/gui/IdCanvas.java @@ -63,10 +63,6 @@ public class IdCanvas extends JPanel implements ViewportListenerI List searchResults; - FontMetrics fm; - - AnnotationLabels labels = null; - AnnotationPanel ap; private Font idfont; @@ -83,12 +79,12 @@ public class IdCanvas extends JPanel implements ViewportListenerI this.av = av; PaintRefresher.Register(this, av.getSequenceSetId()); av.getRanges().addPropertyChangeListener(this); - } + } /** * DOCUMENT ME! * - * @param gg + * @param g * DOCUMENT ME! * @param hiddenRows * true - check and display hidden row marker if need be @@ -101,7 +97,7 @@ public class IdCanvas extends JPanel implements ViewportListenerI * @param ypos * DOCUMENT ME! */ - public void drawIdString(Graphics2D gg, boolean hiddenRows, SequenceI s, + public void drawIdString(Graphics2D g, boolean hiddenRows, SequenceI s, int i, int starty, int ypos) { int xPos = 0; @@ -110,39 +106,40 @@ public class IdCanvas extends JPanel implements ViewportListenerI if ((searchResults != null) && searchResults.contains(s)) { - gg.setColor(Color.black); - gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(), + g.setColor(Color.black); + g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(), charHeight); - gg.setColor(Color.white); + g.setColor(Color.white); } else if ((av.getSelectionGroup() != null) && av.getSelectionGroup().getSequences(null).contains(s)) { - gg.setColor(Color.lightGray); - gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(), + g.setColor(Color.lightGray); + g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(), charHeight); - gg.setColor(Color.white); + g.setColor(Color.white); } else { - gg.setColor(av.getSequenceColour(s)); - gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(), + g.setColor(av.getSequenceColour(s)); + g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(), charHeight); - gg.setColor(Color.black); + g.setColor(Color.black); } if (av.isRightAlignIds()) { + FontMetrics fm = g.getFontMetrics(); xPos = panelWidth - fm.stringWidth(s.getDisplayId(av.getShowJVSuffix())) - 4; } - gg.drawString(s.getDisplayId(av.getShowJVSuffix()), xPos, + g.drawString(s.getDisplayId(av.getShowJVSuffix()), xPos, (((i - starty + 1) * charHeight) + ypos) - (charHeight / 5)); if (hiddenRows) { - drawMarker(i, starty, ypos); + drawMarker(g, av, i, starty, ypos); } } @@ -199,12 +196,16 @@ public class IdCanvas extends JPanel implements ViewportListenerI gg.translate(0, transY); - drawIds(ss, es); + drawIds(gg, av, ss, es, searchResults); gg.translate(0, -transY); fastPaint = true; - repaint(); + + // Call repaint on alignment panel so that repaints from other alignment + // panel components can be aggregated. Otherwise performance of the overview + // window and others may be adversely affected. + av.getAlignPanel().repaint(); } /** @@ -216,192 +217,202 @@ public class IdCanvas extends JPanel implements ViewportListenerI @Override public void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(Color.white); g.fillRect(0, 0, getWidth(), getHeight()); - + if (fastPaint) { fastPaint = false; g.drawImage(image, 0, 0, this); - + return; } - + int oldHeight = imgHeight; - + imgHeight = getHeight(); imgHeight -= (imgHeight % av.getCharHeight()); - + if (imgHeight < 1) { return; } - + if (oldHeight != imgHeight || image.getWidth(this) != getWidth()) { - image = new BufferedImage(getWidth(), imgHeight, - BufferedImage.TYPE_INT_RGB); + image = new BufferedImage(getWidth(), imgHeight, + BufferedImage.TYPE_INT_RGB); } - + gg = (Graphics2D) image.getGraphics(); - + // Fill in the background gg.setColor(Color.white); gg.fillRect(0, 0, getWidth(), imgHeight); - - drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq()); - + + drawIds(gg, av, av.getRanges().getStartSeq(), av.getRanges().getEndSeq(), searchResults); + g.drawImage(image, 0, 0, this); } /** - * DOCUMENT ME! + * Draws sequence ids from sequence index startSeq to endSeq (inclusive), with + * the font and other display settings configured on the viewport. Ids of + * sequences included in the selection are coloured grey, otherwise the + * current id colour for the sequence id is used. * - * @param starty - * DOCUMENT ME! - * @param endy - * DOCUMENT ME! + * @param g + * @param alignViewport + * @param startSeq + * @param endSeq + * @param selection */ - void drawIds(int starty, int endy) + void drawIds(Graphics2D g, AlignViewport alignViewport, final int startSeq, + final int endSeq, List selection) { - if (av.isSeqNameItalics()) + Font font = alignViewport.getFont(); + if (alignViewport.isSeqNameItalics()) { - setIdfont(new Font(av.getFont().getName(), Font.ITALIC, - av.getFont().getSize())); + setIdfont(new Font(font.getName(), Font.ITALIC, + font.getSize())); } else { - setIdfont(av.getFont()); + setIdfont(font); } - gg.setFont(getIdfont()); - fm = gg.getFontMetrics(); + g.setFont(getIdfont()); + FontMetrics fm = g.getFontMetrics(); - if (av.antiAlias) + if (alignViewport.antiAlias) { - gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } Color currentColor = Color.white; Color currentTextColor = Color.black; - boolean hasHiddenRows = av.hasHiddenRows(); + boolean hasHiddenRows = alignViewport.hasHiddenRows(); - if (av.getWrapAlignment()) + if (alignViewport.getWrapAlignment()) { - drawIdsWrapped(starty, hasHiddenRows); + drawIdsWrapped(g, alignViewport, startSeq, getHeight()); return; } - // No need to hang on to labels if we're not wrapped - labels = null; - // Now draw the id strings int panelWidth = getWidth(); int xPos = 0; - SequenceI sequence; // Now draw the id strings - for (int i = starty; i <= endy; i++) + for (int i = startSeq; i <= endSeq; i++) { - sequence = av.getAlignment().getSequenceAt(i); + SequenceI sequence = alignViewport.getAlignment().getSequenceAt(i); if (sequence == null) { continue; } - if (hasHiddenRows || av.isDisplayReferenceSeq()) + if (hasHiddenRows || alignViewport.isDisplayReferenceSeq()) { - setHiddenFont(sequence); + g.setFont(getHiddenFont(sequence, alignViewport)); } // Selected sequence colours - if ((searchResults != null) && searchResults.contains(sequence)) + if (selection != null && selection.contains(sequence)) { currentColor = Color.black; currentTextColor = Color.white; } - else if ((av.getSelectionGroup() != null) && av.getSelectionGroup() - .getSequences(null).contains(sequence)) + else if ((alignViewport.getSelectionGroup() != null) && alignViewport + .getSelectionGroup().getSequences(null).contains(sequence)) { currentColor = Color.lightGray; currentTextColor = Color.black; } else { - currentColor = av.getSequenceColour(sequence); + currentColor = alignViewport.getSequenceColour(sequence); currentTextColor = Color.black; } - gg.setColor(currentColor); + g.setColor(currentColor); - gg.fillRect(0, (i - starty) * av.getCharHeight(), getWidth(), - av.getCharHeight()); + int charHeight = alignViewport.getCharHeight(); + g.fillRect(0, (i - startSeq) * charHeight, + getWidth(), charHeight); - gg.setColor(currentTextColor); + g.setColor(currentTextColor); - String string = sequence.getDisplayId(av.getShowJVSuffix()); + String string = sequence + .getDisplayId(alignViewport.getShowJVSuffix()); - if (av.isRightAlignIds()) + if (alignViewport.isRightAlignIds()) { xPos = panelWidth - fm.stringWidth(string) - 4; } - gg.drawString(string, xPos, - (((i - starty) * av.getCharHeight()) + av.getCharHeight()) - - (av.getCharHeight() / 5)); + g.drawString(string, xPos, (((i - startSeq) * charHeight) + charHeight) + - (charHeight / 5)); if (hasHiddenRows) { - drawMarker(i, starty, 0); + drawMarker(g, alignViewport, i, startSeq, 0); } } } /** - * Draws sequence ids in wrapped mode + * Draws sequence ids, and annotation labels if annotations are shown, in + * wrapped mode * - * @param starty - * @param hasHiddenRows + * @param g + * @param alignViewport + * @param startSeq */ - protected void drawIdsWrapped(int starty, boolean hasHiddenRows) + void drawIdsWrapped(Graphics2D g, AlignViewport alignViewport, + int startSeq, int pageHeight) { - int maxwidth = av.getAlignment().getWidth(); - int alheight = av.getAlignment().getHeight(); + int alignmentWidth = alignViewport.getAlignment().getWidth(); + final int alheight = alignViewport.getAlignment().getHeight(); - if (av.hasHiddenColumns()) + if (alignViewport.hasHiddenColumns()) { - maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth) - 1; + alignmentWidth = alignViewport.getAlignment().getHiddenColumns() + .absoluteToVisibleColumn(alignmentWidth) - 1; } int annotationHeight = 0; - if (av.isShowAnnotation()) + AnnotationLabels labels = null; + if (alignViewport.isShowAnnotation()) { if (ap == null) { - ap = new AnnotationPanel(av); + ap = new AnnotationPanel(alignViewport); } - annotationHeight = ap.adjustPanelHeight(); - if (labels == null) - { - labels = new AnnotationLabels(av); - } + labels = new AnnotationLabels(alignViewport); } - int hgap = av.getCharHeight(); - if (av.getScaleAboveWrapped()) + final int charHeight = alignViewport.getCharHeight(); + int hgap = charHeight; + if (alignViewport.getScaleAboveWrapped()) { - hgap += av.getCharHeight(); + hgap += charHeight; } - int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight; + /* + * height of alignment + gap + annotations (if shown) + */ + int cHeight = alheight * charHeight + hgap + + annotationHeight; - ViewportRanges ranges = av.getRanges(); + ViewportRanges ranges = alignViewport.getRanges(); int rowSize = ranges.getViewportWidth(); @@ -409,49 +420,58 @@ public class IdCanvas extends JPanel implements ViewportListenerI * draw repeating sequence ids until out of sequence data or * out of visible space, whichever comes first */ + boolean hasHiddenRows = alignViewport.hasHiddenRows(); int ypos = hgap; - int row = ranges.getStartRes(); - while ((ypos <= getHeight()) && (row < maxwidth)) + int rowStartRes = ranges.getStartRes(); + while ((ypos <= pageHeight) && (rowStartRes < alignmentWidth)) { - for (int i = starty; i < alheight; i++) + for (int i = startSeq; i < alheight; i++) { - SequenceI s = av.getAlignment().getSequenceAt(i); - if (hasHiddenRows || av.isDisplayReferenceSeq()) + SequenceI s = alignViewport.getAlignment().getSequenceAt(i); + if (hasHiddenRows || alignViewport.isDisplayReferenceSeq()) { - setHiddenFont(s); + g.setFont(getHiddenFont(s, alignViewport)); } else { - gg.setFont(getIdfont()); + g.setFont(getIdfont()); } - - drawIdString(gg, hasHiddenRows, s, i, 0, ypos); + drawIdString(g, hasHiddenRows, s, i, 0, ypos); } - if (labels != null && av.isShowAnnotation()) + if (labels != null && alignViewport.isShowAnnotation()) { - gg.translate(0, ypos + (alheight * av.getCharHeight())); - labels.drawComponent(gg, getWidth()); - gg.translate(0, -ypos - (alheight * av.getCharHeight())); + g.translate(0, ypos + (alheight * charHeight)); + labels.drawComponent(g, getWidth()); + g.translate(0, -ypos - (alheight * charHeight)); } ypos += cHeight; - row += rowSize; + rowStartRes += rowSize; } } - void drawMarker(int i, int starty, int yoffset) + /** + * Draws a marker (a blue right-pointing triangle) between sequences to + * indicate hidden sequences. + * + * @param g + * @param alignViewport + * @param seqIndex + * @param starty + * @param yoffset + */ + void drawMarker(Graphics2D g, AlignViewport alignViewport, int seqIndex, int starty, int yoffset) { - - SequenceI[] hseqs = av.getAlignment() + SequenceI[] hseqs = alignViewport.getAlignment() .getHiddenSequences().hiddenSequences; // Use this method here instead of calling hiddenSeq adjust // 3 times. int hSize = hseqs.length; - int hiddenIndex = i; - int lastIndex = i - 1; - int nextIndex = i + 1; + int hiddenIndex = seqIndex; + int lastIndex = seqIndex - 1; + int nextIndex = seqIndex + 1; for (int j = 0; j < hSize; j++) { @@ -472,52 +492,56 @@ public class IdCanvas extends JPanel implements ViewportListenerI } } + /* + * are we below or above the hidden sequences? + */ boolean below = (hiddenIndex > lastIndex + 1); boolean above = (nextIndex > hiddenIndex + 1); - gg.setColor(Color.blue); + g.setColor(Color.blue); + int charHeight = av.getCharHeight(); + + /* + * vertices of the triangle, below or above hidden seqs + */ + int[] xPoints = new int[] + { getWidth() - charHeight, + getWidth() - charHeight, getWidth() }; + int yShift = seqIndex - starty; + if (below) { - gg.fillPolygon( - new int[] - { getWidth() - av.getCharHeight(), - getWidth() - av.getCharHeight(), getWidth() }, - new int[] - { (i - starty) * av.getCharHeight() + yoffset, - (i - starty) * av.getCharHeight() + yoffset - + av.getCharHeight() / 4, - (i - starty) * av.getCharHeight() + yoffset }, - 3); + int[] yPoints = new int[] { yShift * charHeight + yoffset, + yShift * charHeight + yoffset + charHeight / 4, + yShift * charHeight + yoffset }; + g.fillPolygon(xPoints, yPoints, 3); } if (above) { - gg.fillPolygon( - new int[] - { getWidth() - av.getCharHeight(), - getWidth() - av.getCharHeight(), getWidth() }, - new int[] - { (i - starty + 1) * av.getCharHeight() + yoffset, - (i - starty + 1) * av.getCharHeight() + yoffset - - av.getCharHeight() / 4, - (i - starty + 1) * av.getCharHeight() + yoffset }, - 3); - + yShift++; + int[] yPoints = new int[] { yShift * charHeight + yoffset, + yShift * charHeight + yoffset - charHeight / 4, + yShift * charHeight + yoffset }; + g.fillPolygon(xPoints, yPoints, 3); } } - void setHiddenFont(SequenceI seq) + /** + * Answers the standard sequence id font, or a bold font if the sequence is + * set as reference or a hidden group representative + * + * @param seq + * @param alignViewport + * @return + */ + private Font getHiddenFont(SequenceI seq, AlignViewport alignViewport) { - Font bold = new Font(av.getFont().getName(), Font.BOLD, - av.getFont().getSize()); - if (av.isReferenceSeq(seq) || av.isHiddenRepSequence(seq)) { - gg.setFont(bold); - } - else - { - gg.setFont(getIdfont()); + return new Font(av.getFont().getName(), Font.BOLD, + av.getFont().getSize()); } + return getIdfont(); } /** diff --git a/src/jalview/gui/IdPanel.java b/src/jalview/gui/IdPanel.java index 1f2a3ad..a183144 100755 --- a/src/jalview/gui/IdPanel.java +++ b/src/jalview/gui/IdPanel.java @@ -108,8 +108,7 @@ public class IdPanel extends JPanel SequenceI sequence = av.getAlignment().getSequenceAt(seq); StringBuilder tip = new StringBuilder(64); seqAnnotReport.createTooltipAnnotationReport(tip, sequence, - av.isShowDBRefs(), av.isShowNPFeats(), - sp.seqCanvas.fr.getMinMax()); + av.isShowDBRefs(), av.isShowNPFeats(), sp.seqCanvas.fr); setToolTipText(JvSwingUtils.wrapTooltip(true, sequence.getDisplayId(true) + " " + tip.toString())); } @@ -148,7 +147,8 @@ public class IdPanel extends JPanel public void mouseWheelMoved(MouseWheelEvent e) { e.consume(); - if (e.getWheelRotation() > 0) + double wheelRotation = e.getPreciseWheelRotation(); + if (wheelRotation > 0) { if (e.isShiftDown()) { @@ -159,7 +159,7 @@ public class IdPanel extends JPanel av.getRanges().scrollUp(false); } } - else + else if (wheelRotation < 0) { if (e.isShiftDown()) { @@ -331,7 +331,8 @@ public class IdPanel extends JPanel * and any non-positional features */ List nlinks = Preferences.sequenceUrlLinks.getLinksForMenu(); - for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures()) + List features = sq.getFeatures().getNonPositionalFeatures(); + for (SequenceFeature sf : features) { if (sf.links != null) { @@ -342,7 +343,7 @@ public class IdPanel extends JPanel } } - PopupMenu pop = new PopupMenu(alignPanel, sq, nlinks, + PopupMenu pop = new PopupMenu(alignPanel, sq, features, Preferences.getGroupURLLinks()); pop.show(this, e.getX(), e.getY()); } diff --git a/src/jalview/gui/IdwidthAdjuster.java b/src/jalview/gui/IdwidthAdjuster.java index 8400543..0cffc3b 100755 --- a/src/jalview/gui/IdwidthAdjuster.java +++ b/src/jalview/gui/IdwidthAdjuster.java @@ -23,8 +23,8 @@ package jalview.gui; import jalview.api.AlignViewportI; import java.awt.Color; +import java.awt.Cursor; import java.awt.Graphics; -import java.awt.Image; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; @@ -44,8 +44,6 @@ public class IdwidthAdjuster extends JPanel int oldX = 0; - Image image; - AlignmentPanel ap; /** @@ -57,14 +55,7 @@ public class IdwidthAdjuster extends JPanel public IdwidthAdjuster(AlignmentPanel ap) { this.ap = ap; - - java.net.URL url = getClass().getResource("/images/idwidth.gif"); - - if (url != null) - { - image = java.awt.Toolkit.getDefaultToolkit().createImage(url); - } - + setBackground(Color.white); addMouseListener(this); addMouseMotionListener(this); } @@ -196,10 +187,7 @@ public class IdwidthAdjuster extends JPanel if (active) { - if (image != null) - { - g.drawImage(image, getWidth() - 20, 2, this); - } + setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)); } } } diff --git a/src/jalview/gui/JDatabaseTree.java b/src/jalview/gui/JDatabaseTree.java index 0a6b9d6..8d62433 100644 --- a/src/jalview/gui/JDatabaseTree.java +++ b/src/jalview/gui/JDatabaseTree.java @@ -20,7 +20,6 @@ */ package jalview.gui; -import jalview.bin.Cache; import jalview.util.MessageManager; import jalview.ws.seqfetcher.DbSourceProxy; @@ -42,7 +41,6 @@ import java.util.List; import java.util.Vector; import javax.swing.JButton; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -101,10 +99,10 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener * identical DB sources, and should be collapsed. */ DefaultMutableTreeNode tn = null, root = new DefaultMutableTreeNode(); - Hashtable source = new Hashtable(); + Hashtable source = new Hashtable<>(); sfetcher = sfetch; String dbs[] = sfetch.getSupportedDb(); - Hashtable ht = new Hashtable(); + Hashtable ht = new Hashtable<>(); for (int i = 0; i < dbs.length; i++) { tn = source.get(dbs[i]); @@ -370,7 +368,7 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener tsel = dbviews.getSelectionPaths(); boolean forcedFirstChild = false; - List srcs = new ArrayList(); + List srcs = new ArrayList<>(); if (tsel != null) { for (TreePath tp : tsel) @@ -489,7 +487,7 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener return null; } StringBuffer sb = new StringBuffer(); - HashSet hs = new HashSet(); + HashSet hs = new HashSet<>(); for (DbSourceProxy dbs : getSelectedSources()) { String tq = dbs.getTestQuery(); @@ -506,7 +504,7 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener return sb.toString(); } - List lstners = new Vector(); + List lstners = new Vector<>(); public void addActionListener(ActionListener actionListener) { @@ -518,54 +516,6 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener lstners.remove(actionListener); } - public static void main(String args[]) - { - Cache.getDasSourceRegistry(); - JDatabaseTree jdt = new JDatabaseTree(new jalview.ws.SequenceFetcher()); - JFrame foo = new JFrame(); - foo.setLayout(new BorderLayout()); - foo.add(jdt.getDatabaseSelectorButton(), BorderLayout.CENTER); - foo.pack(); - foo.setVisible(true); - int nultimes = 5; - final Thread us = Thread.currentThread(); - jdt.addActionListener(new ActionListener() - { - - @Override - public void actionPerformed(ActionEvent e) - { - us.interrupt(); - } - }); - do - { - try - { - Thread.sleep(50); - } catch (InterruptedException x) - { - nultimes--; - if (!jdt.hasSelection()) - { - System.out.println("No Selection"); - } - else - { - System.out.println("Selection: " + jdt.getSelectedItem()); - int s = 1; - for (DbSourceProxy pr : jdt.getSelectedSources()) - { - System.out.println("Source " + s++ + ": " + pr.getDbName() - + " (" + pr.getDbSource() + ") Version " - + pr.getDbVersion() + ". Test:\t" + pr.getTestQuery()); - } - System.out.println("Test queries: " + jdt.getExampleQueries()); - } - } - } while (nultimes > 0 && foo.isVisible()); - foo.setVisible(false); - } @Override public void keyPressed(KeyEvent arg0) @@ -596,4 +546,11 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener // TODO Auto-generated method stub } + + @Override + public void setVisible(boolean arg0) + { + System.out.println("setVisible: " + arg0); + super.setVisible(arg0); + } } diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index 4a15024..9285754 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -37,6 +37,10 @@ import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.datamodel.StructureViewerModel; import jalview.datamodel.StructureViewerModel.StructureData; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.ext.varna.RnaModel; import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; @@ -48,6 +52,8 @@ import jalview.schemabinding.version2.Annotation; import jalview.schemabinding.version2.AnnotationColours; import jalview.schemabinding.version2.AnnotationElement; import jalview.schemabinding.version2.CalcIdParam; +import jalview.schemabinding.version2.Colour; +import jalview.schemabinding.version2.CompoundMatcher; import jalview.schemabinding.version2.DBRef; import jalview.schemabinding.version2.Features; import jalview.schemabinding.version2.Group; @@ -60,6 +66,8 @@ import jalview.schemabinding.version2.MapListFrom; import jalview.schemabinding.version2.MapListTo; import jalview.schemabinding.version2.Mapping; import jalview.schemabinding.version2.MappingChoice; +import jalview.schemabinding.version2.MatchCondition; +import jalview.schemabinding.version2.MatcherSet; import jalview.schemabinding.version2.OtherData; import jalview.schemabinding.version2.PdbentryItem; import jalview.schemabinding.version2.Pdbids; @@ -75,6 +83,9 @@ import jalview.schemabinding.version2.ThresholdLine; import jalview.schemabinding.version2.Tree; import jalview.schemabinding.version2.UserColours; import jalview.schemabinding.version2.Viewport; +import jalview.schemabinding.version2.types.ColourThreshTypeType; +import jalview.schemabinding.version2.types.FeatureMatcherByType; +import jalview.schemabinding.version2.types.NoValueColour; import jalview.schemes.AnnotationColourGradient; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemeProperty; @@ -83,10 +94,12 @@ import jalview.schemes.ResidueProperties; import jalview.schemes.UserColourScheme; import jalview.structure.StructureSelectionManager; import jalview.structures.models.AAStructureBindingModel; +import jalview.util.Format; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.util.StringUtils; import jalview.util.jarInputStreamProvider; +import jalview.util.matcher.Condition; import jalview.viewmodel.AlignmentViewport; import jalview.viewmodel.ViewportRanges; import jalview.viewmodel.seqfeatures.FeatureRendererSettings; @@ -115,6 +128,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; @@ -879,15 +893,33 @@ public class Jalview2XML } if (sf.otherDetails != null) { - String key; - Iterator keys = sf.otherDetails.keySet().iterator(); - while (keys.hasNext()) + /* + * save feature attributes, which may be simple strings or + * map valued (have sub-attributes) + */ + for (Entry entry : sf.otherDetails.entrySet()) { - key = keys.next(); - OtherData keyValue = new OtherData(); - keyValue.setKey(key); - keyValue.setValue(sf.otherDetails.get(key).toString()); - features.addOtherData(keyValue); + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Map) + { + for (Entry subAttribute : ((Map) value) + .entrySet()) + { + OtherData otherData = new OtherData(); + otherData.setKey(key); + otherData.setKey2(subAttribute.getKey()); + otherData.setValue(subAttribute.getValue().toString()); + features.addOtherData(otherData); + } + } + else + { + OtherData otherData = new OtherData(); + otherData.setKey(key); + otherData.setValue(value.toString()); + features.addOtherData(otherData); + } } } @@ -1313,19 +1345,33 @@ public class Jalview2XML { jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings(); - String[] renderOrder = ap.getSeqPanel().seqCanvas - .getFeatureRenderer().getRenderOrder() - .toArray(new String[0]); + FeatureRenderer fr = ap.getSeqPanel().seqCanvas + .getFeatureRenderer(); + String[] renderOrder = fr.getRenderOrder().toArray(new String[0]); Vector settingsAdded = new Vector<>(); if (renderOrder != null) { for (String featureType : renderOrder) { - FeatureColourI fcol = ap.getSeqPanel().seqCanvas - .getFeatureRenderer().getFeatureStyle(featureType); Setting setting = new Setting(); setting.setType(featureType); + + /* + * save any filter for the feature type + */ + FeatureMatcherSetI filter = fr.getFeatureFilter(featureType); + if (filter != null) { + Iterator filters = filter.getMatchers().iterator(); + FeatureMatcherI firstFilter = filters.next(); + setting.setMatcherSet(Jalview2XML.marshalFilter( + firstFilter, filters, filter.isAnded())); + } + + /* + * save colour scheme for the feature type + */ + FeatureColourI fcol = fr.getFeatureStyle(featureType); if (!fcol.isSimpleColour()) { setting.setColour(fcol.getMaxColour().getRGB()); @@ -1333,8 +1379,25 @@ public class Jalview2XML setting.setMin(fcol.getMin()); setting.setMax(fcol.getMax()); setting.setColourByLabel(fcol.isColourByLabel()); + if (fcol.isColourByAttribute()) + { + setting.setAttributeName(fcol.getAttributeName()); + } setting.setAutoScale(fcol.isAutoScaled()); setting.setThreshold(fcol.getThreshold()); + Color noColour = fcol.getNoColour(); + if (noColour == null) + { + setting.setNoValueColour(NoValueColour.NONE); + } + else if (noColour.equals(fcol.getMaxColour())) + { + setting.setNoValueColour(NoValueColour.MAX); + } + else + { + setting.setNoValueColour(NoValueColour.MIN); + } // -1 = No threshold, 0 = Below, 1 = Above setting.setThreshstate(fcol.isAboveThreshold() ? 1 : (fcol.isBelowThreshold() ? 0 : -1)); @@ -1346,7 +1409,7 @@ public class Jalview2XML setting.setDisplay( av.getFeaturesDisplayed().isVisible(featureType)); - float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer() + float rorder = fr .getOrder(featureType); if (rorder > -1) { @@ -1358,8 +1421,7 @@ public class Jalview2XML } // is groups actually supposed to be a map here ? - Iterator en = ap.getSeqPanel().seqCanvas - .getFeatureRenderer().getFeatureGroups().iterator(); + Iterator en = fr.getFeatureGroups().iterator(); Vector groupsAdded = new Vector<>(); while (en.hasNext()) { @@ -1370,8 +1432,7 @@ public class Jalview2XML } Group g = new Group(); g.setName(grp); - g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas - .getFeatureRenderer().checkGroupVisibility(grp, false)) + g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false)) .booleanValue()); fs.addGroup(g); groupsAdded.addElement(grp); @@ -1389,9 +1450,10 @@ public class Jalview2XML } else { - ArrayList hiddenRegions = hidden.getHiddenColumnsCopy(); - for (int[] region : hiddenRegions) + Iterator hiddenRegions = hidden.iterator(); + while (hiddenRegions.hasNext()) { + int[] region = hiddenRegions.next(); HiddenColumns hc = new HiddenColumns(); hc.setStart(region[0]); hc.setEnd(region[1]); @@ -2962,19 +3024,46 @@ public class Jalview2XML features[f].getEnd(), features[f].getScore(), features[f].getFeatureGroup()); sf.setStatus(features[f].getStatus()); + + /* + * load any feature attributes - include map-valued attributes + */ + Map> mapAttributes = new HashMap<>(); for (int od = 0; od < features[f].getOtherDataCount(); od++) { OtherData keyValue = features[f].getOtherData(od); - if (keyValue.getKey().startsWith("LINK")) + String attributeName = keyValue.getKey(); + String attributeValue = keyValue.getValue(); + if (attributeName.startsWith("LINK")) { - sf.addLink(keyValue.getValue()); + sf.addLink(attributeValue); } else { - sf.setValue(keyValue.getKey(), keyValue.getValue()); + String subAttribute = keyValue.getKey2(); + if (subAttribute == null) + { + // simple string-valued attribute + sf.setValue(attributeName, attributeValue); + } + else + { + // attribute 'key' has sub-attribute 'key2' + if (!mapAttributes.containsKey(attributeName)) + { + mapAttributes.put(attributeName, new HashMap<>()); + } + mapAttributes.get(attributeName).put(subAttribute, + attributeValue); + } } - } + for (Entry> mapAttribute : mapAttributes + .entrySet()) + { + sf.setValue(mapAttribute.getKey(), mapAttribute.getValue()); + } + // adds feature to datasequence's feature set (since Jalview 2.10) al.getSequenceAt(i).addSequenceFeature(sf); } @@ -4550,9 +4639,11 @@ public class Jalview2XML af.viewport.setShowGroupConservation(false); } - // recover featre settings + // recover feature settings if (jms.getFeatureSettings() != null) { + FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas + .getFeatureRenderer(); FeaturesDisplayed fdi; af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); String[] renderOrder = new String[jms.getFeatureSettings() @@ -4564,14 +4655,51 @@ public class Jalview2XML .getSettingCount(); fs++) { Setting setting = jms.getFeatureSettings().getSetting(fs); + String featureType = setting.getType(); + + /* + * restore feature filters (if any) + */ + MatcherSet filters = setting.getMatcherSet(); + if (filters != null) + { + FeatureMatcherSetI filter = Jalview2XML + .unmarshalFilter(featureType, filters); + if (!filter.isEmpty()) + { + fr.setFeatureFilter(featureType, filter); + } + } + + /* + * restore feature colour scheme + */ + Color maxColour = new Color(setting.getColour()); if (setting.hasMincolour()) { - FeatureColourI gc = setting.hasMin() - ? new FeatureColour(new Color(setting.getMincolour()), - new Color(setting.getColour()), setting.getMin(), - setting.getMax()) - : new FeatureColour(new Color(setting.getMincolour()), - new Color(setting.getColour()), 0, 1); + /* + * minColour is always set unless a simple colour + * (including for colour by label though it doesn't use it) + */ + Color minColour = new Color(setting.getMincolour()); + Color noValueColour = minColour; + NoValueColour noColour = setting.getNoValueColour(); + if (noColour == NoValueColour.NONE) + { + noValueColour = null; + } + else if (noColour == NoValueColour.MAX) + { + noValueColour = maxColour; + } + float min = setting.hasMin() ? setting.getMin() : 0f; + float max = setting.hasMin() ? setting.getMax() : 1f; + FeatureColourI gc = new FeatureColour(minColour, maxColour, + noValueColour, min, max); + if (setting.getAttributeNameCount() > 0) + { + gc.setAttributeName(setting.getAttributeName()); + } if (setting.hasThreshold()) { gc.setThreshold(setting.getThreshold()); @@ -4596,26 +4724,26 @@ public class Jalview2XML gc.setColourByLabel(setting.getColourByLabel()); } // and put in the feature colour table. - featureColours.put(setting.getType(), gc); + featureColours.put(featureType, gc); } else { - featureColours.put(setting.getType(), - new FeatureColour(new Color(setting.getColour()))); + featureColours.put(featureType, + new FeatureColour(maxColour)); } - renderOrder[fs] = setting.getType(); + renderOrder[fs] = featureType; if (setting.hasOrder()) { - featureOrder.put(setting.getType(), setting.getOrder()); + featureOrder.put(featureType, setting.getOrder()); } else { - featureOrder.put(setting.getType(), new Float( + featureOrder.put(featureType, new Float( fs / jms.getFeatureSettings().getSettingCount())); } if (setting.getDisplay()) { - fdi.setVisible(setting.getType()); + fdi.setVisible(featureType); } } Map fgtable = new Hashtable<>(); @@ -4629,9 +4757,7 @@ public class Jalview2XML // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder); FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder, fgtable, featureColours, 1.0f, featureOrder); - af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer() - .transferSettings(frs); - + fr.transferSettings(frs); } if (view.getHiddenColumnsCount() > 0) @@ -5333,7 +5459,7 @@ public class Jalview2XML if (this.frefedSequence == null) { - frefedSequence = new Vector(); + frefedSequence = new Vector<>(); } viewportsAdded.clear(); @@ -5585,4 +5711,289 @@ public class Jalview2XML { return counter++; } + + /** + * Populates an XML model of the feature colour scheme for one feature type + * + * @param featureType + * @param fcol + * @return + */ + protected static jalview.schemabinding.version2.Colour marshalColour( + String featureType, FeatureColourI fcol) + { + jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour(); + if (fcol.isSimpleColour()) + { + col.setRGB(Format.getHexString(fcol.getColour())); + } + else + { + col.setRGB(Format.getHexString(fcol.getMaxColour())); + col.setMin(fcol.getMin()); + col.setMax(fcol.getMax()); + col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour())); + col.setAutoScale(fcol.isAutoScaled()); + col.setThreshold(fcol.getThreshold()); + col.setColourByLabel(fcol.isColourByLabel()); + col.setThreshType(fcol.isAboveThreshold() ? ColourThreshTypeType.ABOVE + : (fcol.isBelowThreshold() ? ColourThreshTypeType.BELOW + : ColourThreshTypeType.NONE)); + if (fcol.isColourByAttribute()) + { + col.setAttributeName(fcol.getAttributeName()); + } + Color noColour = fcol.getNoColour(); + if (noColour == null) + { + col.setNoValueColour(NoValueColour.NONE); + } + else if (noColour == fcol.getMaxColour()) + { + col.setNoValueColour(NoValueColour.MAX); + } + else + { + col.setNoValueColour(NoValueColour.MIN); + } + } + col.setName(featureType); + return col; + } + + /** + * Populates an XML model of the feature filter(s) for one feature type + * + * @param firstMatcher + * the first (or only) match condition) + * @param filter + * remaining match conditions (if any) + * @param and + * if true, conditions are and-ed, else or-ed + */ + protected static MatcherSet marshalFilter(FeatureMatcherI firstMatcher, + Iterator filters, boolean and) + { + MatcherSet result = new MatcherSet(); + + if (filters.hasNext()) + { + /* + * compound matcher + */ + CompoundMatcher compound = new CompoundMatcher(); + compound.setAnd(and); + MatcherSet matcher1 = marshalFilter(firstMatcher, + Collections.emptyIterator(), and); + compound.addMatcherSet(matcher1); + FeatureMatcherI nextMatcher = filters.next(); + MatcherSet matcher2 = marshalFilter(nextMatcher, filters, and); + compound.addMatcherSet(matcher2); + result.setCompoundMatcher(compound); + } + else + { + /* + * single condition matcher + */ + MatchCondition matcherModel = new MatchCondition(); + matcherModel.setCondition( + firstMatcher.getMatcher().getCondition().getStableName()); + matcherModel.setValue(firstMatcher.getMatcher().getPattern()); + if (firstMatcher.isByAttribute()) + { + matcherModel.setBy(FeatureMatcherByType.BYATTRIBUTE); + matcherModel.setAttributeName(firstMatcher.getAttribute()); + } + else if (firstMatcher.isByLabel()) + { + matcherModel.setBy(FeatureMatcherByType.BYLABEL); + } + else if (firstMatcher.isByScore()) + { + matcherModel.setBy(FeatureMatcherByType.BYSCORE); + } + result.setMatchCondition(matcherModel); + } + + return result; + } + + /** + * Loads one XML model of a feature filter to a Jalview object + * + * @param featureType + * @param matcherSetModel + * @return + */ + protected static FeatureMatcherSetI unmarshalFilter( + String featureType, MatcherSet matcherSetModel) + { + FeatureMatcherSetI result = new FeatureMatcherSet(); + try + { + unmarshalFilterConditions(result, matcherSetModel, true); + } catch (IllegalStateException e) + { + // mixing AND and OR conditions perhaps + System.err.println( + String.format("Error reading filter conditions for '%s': %s", + featureType, e.getMessage())); + // return as much as was parsed up to the error + } + + return result; + } + + /** + * Adds feature match conditions to matcherSet as unmarshalled from XML + * (possibly recursively for compound conditions) + * + * @param matcherSet + * @param matcherSetModel + * @param and + * if true, multiple conditions are AND-ed, else they are OR-ed + * @throws IllegalStateException + * if AND and OR conditions are mixed + */ + protected static void unmarshalFilterConditions( + FeatureMatcherSetI matcherSet, MatcherSet matcherSetModel, + boolean and) + { + MatchCondition mc = matcherSetModel.getMatchCondition(); + if (mc != null) + { + /* + * single condition + */ + FeatureMatcherByType filterBy = mc.getBy(); + Condition cond = Condition.fromString(mc.getCondition()); + String pattern = mc.getValue(); + FeatureMatcherI matchCondition = null; + if (filterBy == FeatureMatcherByType.BYLABEL) + { + matchCondition = FeatureMatcher.byLabel(cond, pattern); + } + else if (filterBy == FeatureMatcherByType.BYSCORE) + { + matchCondition = FeatureMatcher.byScore(cond, pattern); + + } + else if (filterBy == FeatureMatcherByType.BYATTRIBUTE) + { + String[] attNames = mc.getAttributeName(); + matchCondition = FeatureMatcher.byAttribute(cond, pattern, + attNames); + } + + /* + * note this throws IllegalStateException if AND-ing to a + * previously OR-ed compound condition, or vice versa + */ + if (and) + { + matcherSet.and(matchCondition); + } + else + { + matcherSet.or(matchCondition); + } + } + else + { + /* + * compound condition + */ + MatcherSet[] matchers = matcherSetModel.getCompoundMatcher() + .getMatcherSet(); + boolean anded = matcherSetModel.getCompoundMatcher().getAnd(); + if (matchers.length == 2) + { + unmarshalFilterConditions(matcherSet, matchers[0], anded); + unmarshalFilterConditions(matcherSet, matchers[1], anded); + } + else + { + System.err.println("Malformed compound filter condition"); + } + } + } + + /** + * Loads one XML model of a feature colour to a Jalview object + * + * @param colourModel + * @return + */ + protected static FeatureColourI unmarshalColour( + jalview.schemabinding.version2.Colour colourModel) + { + FeatureColourI colour = null; + + if (colourModel.hasMax()) + { + Color mincol = null; + Color maxcol = null; + Color noValueColour = null; + + try + { + mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16)); + maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16)); + } catch (Exception e) + { + Cache.log.warn("Couldn't parse out graduated feature color.", e); + } + + NoValueColour noCol = colourModel.getNoValueColour(); + if (noCol == NoValueColour.MIN) + { + noValueColour = mincol; + } + else if (noCol == NoValueColour.MAX) + { + noValueColour = maxcol; + } + + colour = new FeatureColour(mincol, maxcol, noValueColour, + colourModel.getMin(), + colourModel.getMax()); + String[] attributes = colourModel.getAttributeName(); + if (attributes != null && attributes.length > 0) + { + colour.setAttributeName(attributes); + } + if (colourModel.hasAutoScale()) + { + colour.setAutoScaled(colourModel.getAutoScale()); + } + if (colourModel.hasColourByLabel()) + { + colour.setColourByLabel(colourModel.getColourByLabel()); + } + if (colourModel.hasThreshold()) + { + colour.setThreshold(colourModel.getThreshold()); + } + ColourThreshTypeType ttyp = colourModel.getThreshType(); + if (ttyp != null) + { + if (ttyp == ColourThreshTypeType.ABOVE) + { + colour.setAboveThreshold(true); + } + else if (ttyp == ColourThreshTypeType.BELOW) + { + colour.setBelowThreshold(true); + } + } + } + else + { + Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16)); + colour = new FeatureColour(color); + } + + return colour; + } } diff --git a/src/jalview/gui/JalviewDialog.java b/src/jalview/gui/JalviewDialog.java index 05f5ffc..1d7bf3d 100644 --- a/src/jalview/gui/JalviewDialog.java +++ b/src/jalview/gui/JalviewDialog.java @@ -27,8 +27,8 @@ import java.awt.Dimension; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; import javax.swing.JButton; import javax.swing.JDialog; @@ -118,55 +118,14 @@ public abstract class JalviewDialog extends JPanel closeDialog(); } }); - frame.addWindowListener(new WindowListener() + frame.addWindowListener(new WindowAdapter() { - - @Override - public void windowOpened(WindowEvent e) - { - // TODO Auto-generated method stub - - } - - @Override - public void windowIconified(WindowEvent e) - { - // TODO Auto-generated method stub - - } - - @Override - public void windowDeiconified(WindowEvent e) - { - // TODO Auto-generated method stub - - } - - @Override - public void windowDeactivated(WindowEvent e) - { - // TODO Auto-generated method stub - - } - @Override public void windowClosing(WindowEvent e) { // user has cancelled the dialog closeDialog(); } - - @Override - public void windowClosed(WindowEvent e) - { - } - - @Override - public void windowActivated(WindowEvent e) - { - // TODO Auto-generated method stub - - } }); } @@ -177,8 +136,8 @@ public abstract class JalviewDialog extends JPanel { try { - frame.dispose(); raiseClosed(); + frame.dispose(); } catch (Exception ex) { } diff --git a/src/jalview/gui/JvSwingUtils.java b/src/jalview/gui/JvSwingUtils.java index 0a765cb..4658668 100644 --- a/src/jalview/gui/JvSwingUtils.java +++ b/src/jalview/gui/JvSwingUtils.java @@ -24,14 +24,20 @@ import jalview.util.MessageManager; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; import java.awt.Font; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; import java.util.Objects; import javax.swing.AbstractButton; +import javax.swing.BorderFactory; import javax.swing.JButton; +import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenu; @@ -39,6 +45,8 @@ import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.TitledBorder; /** * useful functions for building Swing GUIs @@ -304,4 +312,71 @@ public final class JvSwingUtils comp.setFont(JvSwingUtils.getLabelFont()); } + /** + * A helper method to build a drop-down choice of values, with tooltips for + * the entries + * + * @param entries + * @param tooltips + */ + public static JComboBox buildComboWithTooltips( + List entries, List tooltips) + { + JComboBox combo = new JComboBox<>(); + final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer(); + combo.setRenderer(renderer); + for (String attName : entries) + { + combo.addItem(attName); + } + renderer.setTooltips(tooltips); + final MouseAdapter mouseListener = new MouseAdapter() + { + @Override + public void mouseEntered(MouseEvent e) + { + int j = combo.getSelectedIndex(); + if (j > -1) + { + combo.setToolTipText(tooltips.get(j)); + } + } + @Override + public void mouseExited(MouseEvent e) + { + combo.setToolTipText(null); + } + }; + for (Component c : combo.getComponents()) + { + c.addMouseListener(mouseListener); + } + return combo; + } + + /** + * Adds a titled border to the component in the default font and position (top + * left), optionally witht italic text + * + * @param comp + * @param title + * @param italic + */ + public static TitledBorder createTitledBorder(JComponent comp, + String title, boolean italic) + { + Font font = comp.getFont(); + if (italic) + { + font = new Font(font.getName(), Font.ITALIC, font.getSize()); + } + Border border = BorderFactory.createTitledBorder(""); + TitledBorder titledBorder = BorderFactory.createTitledBorder(border, + title, TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION, + font); + comp.setBorder(titledBorder); + + return titledBorder; + } + } diff --git a/src/jalview/gui/OverviewCanvas.java b/src/jalview/gui/OverviewCanvas.java index 2991889..cc361a5 100644 --- a/src/jalview/gui/OverviewCanvas.java +++ b/src/jalview/gui/OverviewCanvas.java @@ -157,11 +157,9 @@ public class OverviewCanvas extends JComponent { mg.translate(0, od.getSequencesHeight()); or.drawGraph(mg, av.getAlignmentConservationAnnotation(), - av.getCharWidth(), od.getGraphHeight(), - od.getColumns(av.getAlignment())); + od.getGraphHeight(), od.getColumns(av.getAlignment())); mg.translate(0, -od.getSequencesHeight()); } - System.gc(); or.removePropertyChangeListener(progressPanel); or = null; @@ -183,7 +181,7 @@ public class OverviewCanvas extends JComponent @Override public void paintComponent(Graphics g) { - // super.paintComponent(g); + super.paintComponent(g); if (restart) { diff --git a/src/jalview/gui/OverviewPanel.java b/src/jalview/gui/OverviewPanel.java index 43b4310..02d54a8 100755 --- a/src/jalview/gui/OverviewPanel.java +++ b/src/jalview/gui/OverviewPanel.java @@ -170,15 +170,20 @@ public class OverviewPanel extends JPanel @Override public void mouseMoved(MouseEvent evt) { - if (od.isPositionInBox(evt.getX(), evt.getY())) + if (!draggingBox) + // don't bother changing the cursor if we're dragging the box + // as we can't have moved inside or out of the box in that case { - // display drag cursor at mouse position - setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); - } - else - { - // reset cursor - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (od.isPositionInBox(evt.getX(), evt.getY())) + { + // display drag cursor at mouse position + setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + } + else + { + // reset cursor + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } } } }); @@ -203,6 +208,10 @@ public class OverviewPanel extends JPanel if (!od.isPositionInBox(evt.getX(), evt.getY())) { draggingBox = false; + + // display drag cursor at mouse position + setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment().getHiddenSequences(), av.getAlignment().getHiddenColumns()); @@ -225,6 +234,13 @@ public class OverviewPanel extends JPanel showPopupMenu(evt); } } + + @Override + public void mouseReleased(MouseEvent evt) + { + draggingBox = false; + } + }); } diff --git a/src/jalview/gui/PCAPanel.java b/src/jalview/gui/PCAPanel.java index 9f52d26..7ceceee 100644 --- a/src/jalview/gui/PCAPanel.java +++ b/src/jalview/gui/PCAPanel.java @@ -464,7 +464,16 @@ public class PCAPanel extends GPCAPanel public void run() { PrinterJob printJob = PrinterJob.getPrinterJob(); - PageFormat pf = printJob.pageDialog(printJob.defaultPage()); + PageFormat defaultPage = printJob.defaultPage(); + PageFormat pf = printJob.pageDialog(defaultPage); + + if (defaultPage == pf) + { + /* + * user cancelled + */ + return; + } printJob.setPrintable(this, pf); diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 850a09a..ed3d29a 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -34,7 +34,6 @@ import jalview.datamodel.Annotation; import jalview.datamodel.DBRefEntry; import jalview.datamodel.HiddenColumns; import jalview.datamodel.PDBEntry; -import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; @@ -50,6 +49,7 @@ import jalview.schemes.PIDColourScheme; import jalview.util.GroupUrlLink; import jalview.util.GroupUrlLink.UrlStringTooLongException; import jalview.util.MessageManager; +import jalview.util.StringUtils; import jalview.util.UrlLink; import java.awt.Color; @@ -176,25 +176,31 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener * Creates a new PopupMenu object. * * @param ap - * DOCUMENT ME! * @param seq - * DOCUMENT ME! + * @param features + * non-positional features (for seq not null), or positional features + * at residue (for seq equal to null) */ - public PopupMenu(final AlignmentPanel ap, Sequence seq, - List links) + public PopupMenu(final AlignmentPanel ap, SequenceI seq, + List features) { - this(ap, seq, links, null); + this(ap, seq, features, null); } /** + * Constructor * - * @param ap + * @param alignPanel * @param seq - * @param links + * the sequence under the cursor if in the Id panel, null if in the + * sequence panel + * @param features + * non-positional features if in the Id panel, features at the + * clicked residue if in the sequence panel * @param groupLinks */ - public PopupMenu(final AlignmentPanel ap, final SequenceI seq, - List links, List groupLinks) + public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq, + List features, List groupLinks) { // ///////////////////////////////////////////////////////// // If this is activated from the sequence panel, the user may want to @@ -202,7 +208,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener // // If from the IDPanel, we must display the sequence menu // //////////////////////////////////////////////////////// - this.ap = ap; + this.ap = alignPanel; sequence = seq; for (String ff : FileFormats.getInstance().getWritableFormats(true)) @@ -237,9 +243,9 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener /* * And repeat for the current selection group (if there is one): */ - final List selectedGroup = (ap.av.getSelectionGroup() == null + final List selectedGroup = (alignPanel.av.getSelectionGroup() == null ? Collections. emptyList() - : ap.av.getSelectionGroup().getSequences()); + : alignPanel.av.getSelectionGroup().getSequences()); buildAnnotationTypesMenus(groupShowAnnotationsMenu, groupHideAnnotationsMenu, selectedGroup); configureReferenceAnnotationsMenu(groupAddReferenceAnnotations, @@ -257,7 +263,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener if (seq != null) { sequenceMenu.setText(sequence.getName()); - if (seq == ap.av.getAlignment().getSeqrep()) + if (seq == alignPanel.av.getAlignment().getSeqrep()) { makeReferenceSeq.setText( MessageManager.getString("action.unmark_as_reference")); @@ -268,7 +274,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener MessageManager.getString("action.set_as_reference")); } - if (!ap.av.getAlignment().isNucleotide()) + if (!alignPanel.av.getAlignment().isNucleotide()) { remove(rnaStructureMenu); } @@ -279,7 +285,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener * add menu items to 2D-render any alignment or sequence secondary * structure annotation */ - AlignmentAnnotation[] aas = ap.av.getAlignment() + AlignmentAnnotation[] aas = alignPanel.av.getAlignment() .getAlignmentAnnotation(); if (aas != null) { @@ -299,7 +305,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener @Override public void actionPerformed(ActionEvent e) { - new AppVarna(seq, aa, ap); + new AppVarna(seq, aa, alignPanel); } }); rnaStructureMenu.add(menuItem); @@ -328,7 +334,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener public void actionPerformed(ActionEvent e) { // TODO: VARNA does'nt print gaps in the sequence - new AppVarna(seq, aa, ap); + new AppVarna(seq, aa, alignPanel); } }); rnaStructureMenu.add(menuItem); @@ -353,8 +359,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener }); add(menuItem); - if (ap.av.getSelectionGroup() != null - && ap.av.getSelectionGroup().getSize() > 1) + if (alignPanel.av.getSelectionGroup() != null + && alignPanel.av.getSelectionGroup().getSize() > 1) { menuItem = new JMenuItem(MessageManager .formatMessage("label.represent_group_with", new Object[] @@ -370,12 +376,12 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener sequenceMenu.add(menuItem); } - if (ap.av.hasHiddenRows()) + if (alignPanel.av.hasHiddenRows()) { - final int index = ap.av.getAlignment().findIndex(seq); + final int index = alignPanel.av.getAlignment().findIndex(seq); - if (ap.av.adjustForHiddenSeqs(index) - - ap.av.adjustForHiddenSeqs(index - 1) > 1) + if (alignPanel.av.adjustForHiddenSeqs(index) + - alignPanel.av.adjustForHiddenSeqs(index - 1) > 1) { menuItem = new JMenuItem( MessageManager.getString("action.reveal_sequences")); @@ -384,10 +390,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener @Override public void actionPerformed(ActionEvent e) { - ap.av.showSequence(index); - if (ap.overviewPanel != null) + alignPanel.av.showSequence(index); + if (alignPanel.overviewPanel != null) { - ap.overviewPanel.updateOverviewImage(); + alignPanel.overviewPanel.updateOverviewImage(); } } }); @@ -396,7 +402,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener } } // for the case when no sequences are even visible - if (ap.av.hasHiddenRows()) + if (alignPanel.av.hasHiddenRows()) { { menuItem = new JMenuItem( @@ -406,10 +412,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener @Override public void actionPerformed(ActionEvent e) { - ap.av.showAllHiddenSeqs(); - if (ap.overviewPanel != null) + alignPanel.av.showAllHiddenSeqs(); + if (alignPanel.overviewPanel != null) { - ap.overviewPanel.updateOverviewImage(); + alignPanel.overviewPanel.updateOverviewImage(); } } }); @@ -418,9 +424,9 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener } } - SequenceGroup sg = ap.av.getSelectionGroup(); + SequenceGroup sg = alignPanel.av.getSelectionGroup(); boolean isDefinedGroup = (sg != null) - ? ap.av.getAlignment().getGroups().contains(sg) + ? alignPanel.av.getAlignment().getGroups().contains(sg) : false; if (sg != null && sg.getSize() > 0) @@ -458,7 +464,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener Hashtable pdbe = new Hashtable<>(), reppdb = new Hashtable<>(); SequenceI sqass = null; - for (SequenceI sq : ap.av.getSequenceSelection()) + for (SequenceI sq : alignPanel.av.getSequenceSelection()) { Vector pes = sq.getDatasetSequence().getAllPDBEntries(); if (pes != null && pes.size() > 0) @@ -508,24 +514,133 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener rnaStructureMenu.setVisible(false); } - if (links != null && links.size() > 0) + addLinks(seq, features); + + if (seq == null) + { + addFeatureDetails(features); + } + } + + /** + * Add a link to show feature details for each sequence feature + * + * @param features + */ + protected void addFeatureDetails(List features) + { + if (features == null || features.isEmpty()) { - addFeatureLinks(seq, links); + return; } + JMenu details = new JMenu( + MessageManager.getString("label.feature_details")); + add(details); + + for (final SequenceFeature sf : features) + { + int start = sf.getBegin(); + int end = sf.getEnd(); + String desc = null; + if (start == end) + { + desc = String.format("%s %d", sf.getType(), start); + } + else + { + desc = String.format("%s %d-%d", sf.getType(), start, end); + } + String tooltip = desc; + String description = sf.getDescription(); + if (description != null) + { + description = StringUtils.stripHtmlTags(description); + if (description.length() > 12) + { + desc = desc + " " + description.substring(0, 12) + ".."; + } + else + { + desc = desc + " " + description; + } + tooltip = tooltip + " " + description; + } + if (sf.getFeatureGroup() != null) + { + tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")"); + } + JMenuItem item = new JMenuItem(desc); + item.setToolTipText(tooltip); + item.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + showFeatureDetails(sf); + } + }); + details.add(item); + } + } + + /** + * Opens a panel showing a text report of feature dteails + * + * @param sf + */ + protected void showFeatureDetails(SequenceFeature sf) + { + CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer(); + // it appears Java's CSS does not support border-collaps :-( + cap.addStylesheetRule("table { border-collapse: collapse;}"); + cap.addStylesheetRule("table, td, th {border: 1px solid black;}"); + cap.setText(sf.getDetailsReport()); + + Desktop.addInternalFrame(cap, + MessageManager.getString("label.feature_details"), 500, 500); } /** * Adds a 'Link' menu item with a sub-menu item for each hyperlink provided. + * When seq is not null, these are links for the sequence id, which may be to + * external web sites for the sequence accession, and/or links embedded in + * non-positional features. When seq is null, only links embedded in the + * provided features are added. * * @param seq - * @param links + * @param features */ - void addFeatureLinks(final SequenceI seq, List links) + void addLinks(final SequenceI seq, List features) { JMenu linkMenu = new JMenu(MessageManager.getString("action.link")); + + List nlinks = null; + if (seq != null) + { + nlinks = Preferences.sequenceUrlLinks.getLinksForMenu(); + } + else + { + nlinks = new ArrayList<>(); + } + + if (features != null) + { + for (SequenceFeature sf : features) + { + if (sf.links != null) + { + for (String link : sf.links) + { + nlinks.add(link); + } + } + } + } + Map> linkset = new LinkedHashMap<>(); - for (String link : links) + for (String link : nlinks) { UrlLink urlLink = null; try @@ -548,25 +663,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener addshowLinks(linkMenu, linkset.values()); - // disable link menu if there are no valid entries + // only add link menu if it has entries if (linkMenu.getItemCount() > 0) { - linkMenu.setEnabled(true); - } - else - { - linkMenu.setEnabled(false); - } - - if (sequence != null) - { - sequenceMenu.add(linkMenu); - } - else - { - add(linkMenu); + if (sequence != null) + { + sequenceMenu.add(linkMenu); + } + else + { + add(linkMenu); + } } - } /** @@ -1453,15 +1561,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener protected void hideInsertions_actionPerformed(ActionEvent actionEvent) { - - HiddenColumns hidden = new HiddenColumns(); - BitSet inserts = new BitSet(), mask = new BitSet(); - - // set mask to preserve existing hidden columns outside selected group - if (ap.av.hasHiddenColumns()) - { - ap.av.getAlignment().getHiddenColumns().markHiddenRegions(mask); - } + HiddenColumns hidden = ap.av.getAlignment().getHiddenColumns(); + BitSet inserts = new BitSet(); boolean markedPopup = false; // mark inserts in current selection @@ -1469,10 +1570,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener { // mark just the columns in the selection group to be hidden inserts.set(ap.av.getSelectionGroup().getStartRes(), - ap.av.getSelectionGroup().getEndRes() + 1); - - // and clear that part of the mask - mask.andNot(inserts); + ap.av.getSelectionGroup().getEndRes() + 1); // TODO why +1? // now clear columns without gaps for (SequenceI sq : ap.av.getSelectionGroup().getSequences()) @@ -1483,29 +1581,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener } inserts.and(sq.getInsertionsAsBits()); } - } - else - { - // initially, mark all columns to be hidden - inserts.set(0, ap.av.getAlignment().getWidth()); - - // and clear out old hidden regions completely - mask.clear(); + hidden.clearAndHideColumns(inserts, ap.av.getSelectionGroup().getStartRes(), + ap.av.getSelectionGroup().getEndRes()); } // now mark for sequence under popup if we haven't already done it - if (!markedPopup && sequence != null) + else if (!markedPopup && sequence != null) { - inserts.and(sequence.getInsertionsAsBits()); - } - - // finally, preserve hidden regions outside selection - inserts.or(mask); + inserts.or(sequence.getInsertionsAsBits()); - // and set hidden columns accordingly - hidden.hideMarkedBits(inserts); - - ap.av.getAlignment().setHiddenColumns(hidden); + // and set hidden columns accordingly + hidden.hideColumns(inserts); + } refresh(); } @@ -1530,10 +1617,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener new Object[] { seq.getDisplayId(true) }) + "

            "); new SequenceAnnotationReport(null).createSequenceAnnotationReport( - contents, seq, true, true, - (ap.getSeqPanel().seqCanvas.fr != null) - ? ap.getSeqPanel().seqCanvas.fr.getMinMax() - : null); + contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr); contents.append("

            "); } cap.setText("" + contents.toString() + ""); diff --git a/src/jalview/gui/Preferences.java b/src/jalview/gui/Preferences.java index aa8369a..7d02fac 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -168,8 +168,6 @@ public class Preferences extends GPreferences JInternalFrame frame; - DasSourceBrowser dasSource; - private WsPreferences wsPrefs; private OptionsParam promptEachTimeOpt = new OptionsParam( @@ -190,8 +188,6 @@ public class Preferences extends GPreferences super(); frame = new JInternalFrame(); frame.setContentPane(this); - dasSource = new DasSourceBrowser(); - dasTab.add(dasSource, BorderLayout.CENTER); wsPrefs = new WsPreferences(); wsTab.add(wsPrefs, BorderLayout.CENTER); int width = 500, height = 450; @@ -525,7 +521,7 @@ public class Preferences extends GPreferences autoIdWidth.setSelected(Cache.getDefault("FIGURE_AUTOIDWIDTH", false)); userIdWidth.setEnabled(!autoIdWidth.isSelected()); userIdWidthlabel.setEnabled(!autoIdWidth.isSelected()); - Integer wi = Cache.getIntegerProperty("FIGURE_USERIDWIDTH"); + Integer wi = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH"); userIdWidth.setText(wi == null ? "" : wi.toString()); // TODO: refactor to use common enum via FormatAdapter and allow extension // for new flat file formats @@ -784,7 +780,7 @@ public class Preferences extends GPreferences Cache.applicationProperties.setProperty("FIGURE_AUTOIDWIDTH", Boolean.toString(autoIdWidth.isSelected())); userIdWidth_actionPerformed(); - Cache.applicationProperties.setProperty("FIGURE_USERIDWIDTH", + Cache.applicationProperties.setProperty("FIGURE_FIXEDIDWIDTH", userIdWidth.getText()); /* @@ -797,7 +793,6 @@ public class Preferences extends GPreferences Cache.applicationProperties.setProperty("PAD_GAPS", Boolean.toString(padGaps.isSelected())); - dasSource.saveProperties(Cache.applicationProperties); wsPrefs.updateAndRefreshWsMenuConfig(false); Cache.saveProperties(); Desktop.instance.doConfigureStructurePrefs(); diff --git a/src/jalview/gui/RotatableCanvas.java b/src/jalview/gui/RotatableCanvas.java index 4ef18d4..02368df 100755 --- a/src/jalview/gui/RotatableCanvas.java +++ b/src/jalview/gui/RotatableCanvas.java @@ -128,7 +128,6 @@ public class RotatableCanvas extends JPanel implements MouseListener, boolean applyToAllViews = false; - // Controller controller; public RotatableCanvas(AlignmentPanel ap) { this.av = ap.av; @@ -136,16 +135,23 @@ public class RotatableCanvas extends JPanel implements MouseListener, addMouseWheelListener(new MouseWheelListener() { + @Override public void mouseWheelMoved(MouseWheelEvent e) { - if (e.getWheelRotation() > 0) + double wheelRotation = e.getPreciseWheelRotation(); + if (wheelRotation > 0) { + /* + * zoom in + */ scale = (float) (scale * 1.1); repaint(); } - - else + else if (wheelRotation < 0) { + /* + * zoom out + */ scale = (float) (scale * 0.9); repaint(); } @@ -162,6 +168,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, boolean first = true; + @Override public void setPoints(Vector points, int npoint) { this.points = points; @@ -327,7 +334,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, dim = height; } - return (float) ((dim * scalefactor) / (2 * maxwidth)); + return (dim * scalefactor) / (2 * maxwidth); } /** @@ -352,6 +359,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * * @return DOCUMENT ME! */ + @Override public Dimension getPreferredSize() { if (prefsize != null) @@ -369,6 +377,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * * @return DOCUMENT ME! */ + @Override public Dimension getMinimumSize() { return getPreferredSize(); @@ -380,6 +389,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param g * DOCUMENT ME! */ + @Override public void paintComponent(Graphics g1) { @@ -475,8 +485,8 @@ public class RotatableCanvas extends JPanel implements MouseListener, for (int i = 0; i < npoint; i++) { SequencePoint sp = (SequencePoint) points.elementAt(i); - int x = (int) ((float) (sp.coord[0] - centre[0]) * scale) + halfwidth; - int y = (int) ((float) (sp.coord[1] - centre[1]) * scale) + int x = (int) ((sp.coord[0] - centre[0]) * scale) + halfwidth; + int y = (int) ((sp.coord[1] - centre[1]) * scale) + halfheight; float z = sp.coord[1] - centre[2]; @@ -547,6 +557,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void keyTyped(KeyEvent evt) { } @@ -557,6 +568,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void keyReleased(KeyEvent evt) { } @@ -567,6 +579,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void keyPressed(KeyEvent evt) { if (evt.getKeyCode() == KeyEvent.VK_UP) @@ -598,6 +611,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void mouseClicked(MouseEvent evt) { } @@ -608,6 +622,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void mouseEntered(MouseEvent evt) { } @@ -618,6 +633,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void mouseExited(MouseEvent evt) { } @@ -628,6 +644,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void mouseReleased(MouseEvent evt) { } @@ -638,6 +655,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void mousePressed(MouseEvent evt) { int x = evt.getX(); @@ -690,6 +708,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, // controller.handleSequenceSelectionEvent(new // SequenceSelectionEvent(this,sel)); // } + @Override public void mouseMoved(MouseEvent evt) { SequenceI found = findPoint(evt.getX(), evt.getY()); @@ -710,6 +729,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, * @param evt * DOCUMENT ME! */ + @Override public void mouseDragged(MouseEvent evt) { mx = evt.getX(); @@ -725,8 +745,8 @@ public class RotatableCanvas extends JPanel implements MouseListener, { rotmat.setIdentity(); - rotmat.rotate((float) (my - omy), 'x'); - rotmat.rotate((float) (mx - omx), 'y'); + rotmat.rotate(my - omy, 'x'); + rotmat.rotate(mx - omx, 'y'); for (int i = 0; i < npoint; i++) { @@ -774,9 +794,9 @@ public class RotatableCanvas extends JPanel implements MouseListener, { SequencePoint sp = (SequencePoint) points.elementAt(i); int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale) - + ((float) getWidth() / 2.0)); + + (getWidth() / 2.0)); int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale) - + ((float) getHeight() / 2.0)); + + (getHeight() / 2.0)); if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2)) { @@ -816,9 +836,9 @@ public class RotatableCanvas extends JPanel implements MouseListener, for (int i = 0; i < npoint; i++) { SequencePoint sp = (SequencePoint) points.elementAt(i); - int px = (int) ((float) (sp.coord[0] - centre[0]) * scale) + int px = (int) ((sp.coord[0] - centre[0]) * scale) + halfwidth; - int py = (int) ((float) (sp.coord[1] - centre[1]) * scale) + int py = (int) ((sp.coord[1] - centre[1]) * scale) + halfheight; if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3)) diff --git a/src/jalview/gui/ScalePanel.java b/src/jalview/gui/ScalePanel.java index 798c833..e6bba02 100755 --- a/src/jalview/gui/ScalePanel.java +++ b/src/jalview/gui/ScalePanel.java @@ -42,6 +42,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.beans.PropertyChangeEvent; +import java.util.Iterator; import java.util.List; import javax.swing.JMenuItem; @@ -112,7 +113,7 @@ public class ScalePanel extends JPanel if (av.hasHiddenColumns()) { - x = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x); + x = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(x); } if (x >= av.getAlignment().getWidth()) @@ -174,7 +175,7 @@ public class ScalePanel extends JPanel }); pop.add(item); - if (av.getAlignment().getHiddenColumns().hasHiddenColumns()) + if (av.getAlignment().getHiddenColumns().hasMultiHiddenColumnRegions()) { item = new JMenuItem(MessageManager.getString("action.reveal_all")); item.addActionListener(new ActionListener() @@ -281,7 +282,7 @@ public class ScalePanel extends JPanel if (av.hasHiddenColumns()) { res = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(res); + .visibleToAbsoluteColumn(res); } if (res >= av.getAlignment().getWidth()) @@ -336,7 +337,7 @@ public class ScalePanel extends JPanel int res = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes(); res = Math.max(0, res); - res = hidden.adjustForHiddenColumns(res); + res = hidden.visibleToAbsoluteColumn(res); res = Math.min(res, av.getAlignment().getWidth() - 1); min = Math.min(res, min); max = Math.max(res, max); @@ -392,7 +393,7 @@ public class ScalePanel extends JPanel reveal = av.getAlignment().getHiddenColumns() .getRegionWithEdgeAtRes(res); - res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res); + res = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(res); ToolTipManager.sharedInstance().registerComponent(this); this.setToolTipText( @@ -409,6 +410,8 @@ public class ScalePanel extends JPanel @Override public void paintComponent(Graphics g) { + super.paintComponent(g); + /* * shouldn't get called in wrapped mode as the scale above is * drawn instead by SeqCanvas.drawNorthScale @@ -457,7 +460,7 @@ public class ScalePanel extends JPanel { if (hidden.isVisible(sel)) { - sel = hidden.findColumnPosition(sel); + sel = hidden.absoluteToVisibleColumn(sel); } else { @@ -487,23 +490,18 @@ public class ScalePanel extends JPanel if (av.getShowHiddenMarkers()) { - List positions = hidden.findHiddenRegionPositions(); - for (int pos : positions) + Iterator it = hidden.getStartRegionIterator(startx, + startx + widthx + 1); + while (it.hasNext()) { - res = pos - startx; - - if (res < 0 || res > widthx) - { - continue; - } + res = it.next() - startx; gg.fillPolygon( new int[] - { -1 + res * avCharWidth - avCharHeight / 4, - -1 + res * avCharWidth + avCharHeight / 4, - -1 + res * avCharWidth }, - new int[] - { y, y, y + 2 * yOf }, 3); + { -1 + res * avCharWidth - avCharHeight / 4, + -1 + res * avCharWidth + avCharHeight / 4, + -1 + res * avCharWidth }, new int[] + { y, y, y + 2 * yOf }, 3); } } } @@ -554,7 +552,11 @@ public class ScalePanel extends JPanel || evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT)) { // scroll event, repaint panel - repaint(); + + // Call repaint on alignment panel so that repaints from other alignment + // panel components can be aggregated. Otherwise performance of the overview + // window and others may be adversely affected. + av.getAlignPanel().repaint(); } } diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index 1e1105f..5c404f0 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -25,13 +25,13 @@ import jalview.datamodel.HiddenColumns; import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; +import jalview.datamodel.VisibleContigsIterator; import jalview.renderer.ScaleRenderer; import jalview.renderer.ScaleRenderer.ScaleMark; import jalview.util.Comparison; import jalview.viewmodel.ViewportListenerI; import jalview.viewmodel.ViewportRanges; -import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; @@ -42,6 +42,7 @@ import java.awt.RenderingHints; import java.awt.Shape; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; +import java.util.Iterator; import java.util.List; import javax.swing.JComponent; @@ -198,8 +199,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.hasHiddenColumns()) { HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns(); - startX = hiddenColumns.adjustForHiddenColumns(startx); - endX = hiddenColumns.adjustForHiddenColumns(endx); + startX = hiddenColumns.visibleToAbsoluteColumn(startx); + endX = hiddenColumns.visibleToAbsoluteColumn(endx); } FontMetrics fm = getFontMetrics(av.getFont()); @@ -295,7 +296,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int endSeq = ranges.getEndSeq(); int transX = 0; int transY = 0; - + gg.copyArea(horizontal * charWidth, vertical * charHeight, img.getWidth(), img.getHeight(), -horizontal * charWidth, -vertical * charHeight); @@ -337,7 +338,10 @@ public class SeqCanvas extends JComponent implements ViewportListenerI drawPanel(gg, startRes, endRes, startSeq, endSeq, 0); gg.translate(-transX, -transY); - repaint(); + // Call repaint on alignment panel so that repaints from other alignment + // panel components can be aggregated. Otherwise performance of the + // overview window and others may be adversely affected. + av.getAlignPanel().repaint(); } finally { fastpainting = false; @@ -351,53 +355,49 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int charHeight = av.getCharHeight(); int charWidth = av.getCharWidth(); - + ViewportRanges ranges = av.getRanges(); - + int width = getWidth(); int height = getHeight(); - + width -= (width % charWidth); height -= (height % charHeight); - - // selectImage is the selection group outline image - BufferedImage selectImage = drawSelectionGroup( - ranges.getStartRes(), ranges.getEndRes(), - ranges.getStartSeq(), ranges.getEndSeq()); - + if ((img != null) && (fastPaint || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g.getClipBounds().height))) { - BufferedImage lcimg = buildLocalImage(selectImage); - g.drawImage(lcimg, 0, 0, this); + g.drawImage(img, 0, 0, this); + + drawSelectionGroup((Graphics2D) g, ranges.getStartRes(), + ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq()); + fastPaint = false; } - else if ((width > 0) && (height > 0)) + else if (width > 0 && height > 0) { - // img is a cached version of the last view we drew, if any - // if we have no img or the size has changed, make a new one + /* + * img is a cached version of the last view we drew, if any + * if we have no img or the size has changed, make a new one + */ if (img == null || width != img.getWidth() || height != img.getHeight()) { - img = setupImage(); - if (img == null) - { - return; - } + img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); gg = (Graphics2D) img.getGraphics(); gg.setFont(av.getFont()); } - + if (av.antiAlias) { gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } - + gg.setColor(Color.white); gg.fillRect(0, 0, img.getWidth(), img.getHeight()); - + if (av.getWrapAlignment()) { drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes()); @@ -408,10 +408,10 @@ public class SeqCanvas extends JComponent implements ViewportListenerI ranges.getStartSeq(), ranges.getEndSeq(), 0); } - // lcimg is a local *copy* of img which we'll draw selectImage on top of - BufferedImage lcimg = buildLocalImage(selectImage); - g.drawImage(lcimg, 0, 0, this); + drawSelectionGroup(gg, ranges.getStartRes(), + ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq()); + g.drawImage(img, 0, 0, this); } if (av.cursorMode) @@ -440,14 +440,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { drawPanel(g1, startRes, endRes, startSeq, endSeq, 0); - BufferedImage selectImage = drawSelectionGroup(startRes, endRes, + drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq); - if (selectImage != null) - { - ((Graphics2D) g1).setComposite(AlphaComposite - .getInstance(AlphaComposite.SRC_OVER)); - g1.drawImage(selectImage, 0, 0, this); - } } /** @@ -465,99 +459,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth, int canvasHeight, int startRes) { - SequenceGroup group = av.getSelectionGroup(); - drawWrappedPanel(g, canvasWidth, canvasHeight, startRes); + SequenceGroup group = av.getSelectionGroup(); if (group != null) { - BufferedImage selectImage = null; - try - { - selectImage = new BufferedImage(canvasWidth, canvasHeight, - BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works - } catch (OutOfMemoryError er) - { - System.gc(); - System.err.println("Print image OutOfMemory Error.\n" + er); - new OOMWarning("Creating wrapped alignment image for printing", er); - } - if (selectImage != null) - { - Graphics2D g2 = selectImage.createGraphics(); - setupSelectionGroup(g2, selectImage); - drawWrappedSelection(g2, group, canvasWidth, canvasHeight, + drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight, startRes); - - g2.setComposite( - AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); - g.drawImage(selectImage, 0, 0, this); - g2.dispose(); - } - } - } - - /* - * Make a local image by combining the cached image img - * with any selection - */ - private BufferedImage buildLocalImage(BufferedImage selectImage) - { - // clone the cached image - BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(), - img.getType()); - Graphics2D g2d = lcimg.createGraphics(); - g2d.drawImage(img, 0, 0, null); - - // overlay selection group on lcimg - if (selectImage != null) - { - g2d.setComposite( - AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); - g2d.drawImage(selectImage, 0, 0, this); - } - - g2d.dispose(); - - return lcimg; - } - - /* - * Set up a buffered image of the correct height and size for the sequence canvas - */ - private BufferedImage setupImage() - { - BufferedImage lcimg = null; - - int charWidth = av.getCharWidth(); - int charHeight = av.getCharHeight(); - - int width = getWidth(); - int height = getHeight(); - - width -= (width % charWidth); - height -= (height % charHeight); - - if ((width < 1) || (height < 1)) - { - return null; - } - - try - { - lcimg = new BufferedImage(width, height, - BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works - } catch (OutOfMemoryError er) - { - System.gc(); - System.err.println( - "Group image OutOfMemory Redraw Error.\n" + er); - new OOMWarning("Creating alignment image for display", er); - - return null; } - - return lcimg; } /** @@ -644,8 +553,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI ViewportRanges ranges = av.getRanges(); ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues); + // we need to call this again to make sure the startColumn + + // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths + // correctly. + calculateWrappedGeometry(canvasWidth, canvasHeight); + /* - * draw one width at a time (including any scales or annotation shown), + * draw one width at a time (excluding any scales or annotation shown), * until we have run out of either alignment or vertical space available */ int ypos = wrappedSpaceAboveAlignment; @@ -696,7 +610,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI */ wrappedRepeatHeightPx = wrappedSpaceAboveAlignment; // add sequences - wrappedRepeatHeightPx += av.getRanges().getViewportHeight() + wrappedRepeatHeightPx += av.getAlignment().getHeight() * charHeight; // add annotations panel height if shown wrappedRepeatHeightPx += getAnnotationHeight(); @@ -890,11 +804,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int charWidth = av.getCharWidth(); g.setColor(Color.blue); + int res; HiddenColumns hidden = av.getAlignment().getHiddenColumns(); - List positions = hidden.findHiddenRegionPositions(); - for (int pos : positions) + + Iterator it = hidden.getStartRegionIterator(startColumn, + endColumn); + while (it.hasNext()) { - int res = pos - startColumn; + res = it.next() - startColumn; if (res < 0 || res > endColumn - startColumn + 1) { @@ -943,11 +860,15 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.hasHiddenColumns()) { maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth); + .absoluteToVisibleColumn(maxwidth); } // chop the wrapped alignment extent up into panel-sized blocks and treat // each block as if it were a block from an unwrapped alignment + g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_ROUND, 3f, new float[] + { 5f, 3f }, 0f)); + g.setColor(Color.RED); while ((ypos <= canvasHeight) && (startx < maxwidth)) { // set end value to be start + width, or maxwidth, whichever is smaller @@ -972,6 +893,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI // update horizontal offset startx += cWidth; } + g.setStroke(new BasicStroke()); } int getAnnotationHeight() @@ -1021,29 +943,23 @@ public class SeqCanvas extends JComponent implements ViewportListenerI else { int screenY = 0; - final int screenYMax = endRes - startRes; - int blockStart = startRes; - int blockEnd = endRes; + int blockStart; + int blockEnd; - for (int[] region : av.getAlignment().getHiddenColumns() - .getHiddenColumnsCopy()) - { - int hideStart = region[0]; - int hideEnd = region[1]; + HiddenColumns hidden = av.getAlignment().getHiddenColumns(); + VisibleContigsIterator regions = hidden + .getVisContigsIterator(startRes, endRes + 1, true); - if (hideStart <= blockStart) - { - blockStart += (hideEnd - hideStart) + 1; - continue; - } + while (regions.hasNext()) + { + int[] region = regions.next(); + blockEnd = region[1]; + blockStart = region[0]; /* * draw up to just before the next hidden region, or the end of * the visible region, whichever comes first */ - blockEnd = Math.min(hideStart - 1, blockStart + screenYMax - - screenY); - g1.translate(screenY * charWidth, 0); draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset); @@ -1052,7 +968,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI * draw the downline of the hidden column marker (ScalePanel draws the * triangle on top) if we reached it */ - if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1) + if (av.getShowHiddenMarkers() + && (regions.hasNext() || regions.endsAtHidden())) { g1.setColor(Color.blue); @@ -1063,23 +980,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI g1.translate(-screenY * charWidth, 0); screenY += blockEnd - blockStart + 1; - blockStart = hideEnd + 1; - - if (screenY > screenYMax) - { - // already rendered last block - return; - } - } - - if (screenY <= screenYMax) - { - // remaining visible region to render - blockEnd = blockStart + screenYMax - screenY; - g1.translate(screenY * charWidth, 0); - draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset); - - g1.translate(-screenY * charWidth, 0); } } @@ -1137,16 +1037,16 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.hasSearchResults()) { SearchResultsI searchResults = av.getSearchResults(); - int[] visibleResults = searchResults.getResults(nextSeq, - startRes, endRes); + int[] visibleResults = searchResults.getResults(nextSeq, startRes, + endRes); if (visibleResults != null) { for (int r = 0; r < visibleResults.length; r += 2) { seqRdr.drawHighlightedText(nextSeq, visibleResults[r], - visibleResults[r + 1], (visibleResults[r] - startRes) - * charWidth, offset - + ((i - startSeq) * charHeight)); + visibleResults[r + 1], + (visibleResults[r] - startRes) * charWidth, + offset + ((i - startSeq) * charHeight)); } } } @@ -1160,14 +1060,21 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } + /** + * Draws the outlines of any groups defined on the alignment (excluding the + * current selection group, if any) + * + * @param g1 + * @param startRes + * @param endRes + * @param startSeq + * @param endSeq + * @param offset + */ void drawGroupsBoundaries(Graphics g1, int startRes, int endRes, int startSeq, int endSeq, int offset) { Graphics2D g = (Graphics2D) g1; - // - // /////////////////////////////////// - // Now outline any areas if necessary - // /////////////////////////////////// SequenceGroup group = null; int groupIndex = -1; @@ -1180,18 +1087,15 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (group != null) { - g.setStroke(new BasicStroke()); - g.setColor(group.getOutlineColour()); - do { + g.setColor(group.getOutlineColour()); + drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq, offset); groupIndex++; - g.setStroke(new BasicStroke()); - if (groupIndex >= av.getAlignment().getGroups().size()) { break; @@ -1205,33 +1109,28 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } - - /* - * Draw the selection group as a separate image and overlay + /** + * Draws the outline of the current selection group (if any) + * + * @param g + * @param startRes + * @param endRes + * @param startSeq + * @param endSeq */ - private BufferedImage drawSelectionGroup(int startRes, int endRes, + private void drawSelectionGroup(Graphics2D g, int startRes, int endRes, int startSeq, int endSeq) { - // get a new image of the correct size - BufferedImage selectionImage = setupImage(); - - if (selectionImage == null) - { - return null; - } - SequenceGroup group = av.getSelectionGroup(); if (group == null) { - // nothing to draw - return null; + return; } - // set up drawing colour - Graphics2D g = (Graphics2D) selectionImage.getGraphics(); - - setupSelectionGroup(g, selectionImage); - + g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_ROUND, 3f, new float[] + { 5f, 3f }, 0f)); + g.setColor(Color.RED); if (!av.getWrapAlignment()) { drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq, @@ -1242,9 +1141,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI drawWrappedSelection(g, group, getWidth(), getHeight(), av.getRanges().getStartRes()); } - - g.dispose(); - return selectionImage; + g.setStroke(new BasicStroke()); } /** @@ -1278,7 +1175,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI // convert the cursorX into a position on the visible alignment int cursor_xpos = av.getAlignment().getHiddenColumns() - .findColumnPosition(cursorX); + .absoluteToVisibleColumn(cursorX); if (av.getAlignment().getHiddenColumns().isVisible(cursorX)) { @@ -1335,33 +1232,23 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } - /* - * Set up graphics for selection group - */ - private void setupSelectionGroup(Graphics2D g, - BufferedImage selectionImage) - { - // set background to transparent - g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f)); - g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight()); - - // set up foreground to draw red dashed line - g.setComposite(AlphaComposite.Src); - g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_ROUND, 3f, new float[] - { 5f, 3f }, 0f)); - g.setColor(Color.RED); - } - - /* + /** * Draw a selection group over an unwrapped alignment - * @param g graphics object to draw with - * @param group selection group - * @param startRes start residue of area to draw - * @param endRes end residue of area to draw - * @param startSeq start sequence of area to draw - * @param endSeq end sequence of area to draw - * @param offset vertical offset (used when called from wrapped alignment code) + * + * @param g + * graphics object to draw with + * @param group + * selection group + * @param startRes + * start residue of area to draw + * @param endRes + * end residue of area to draw + * @param startSeq + * start sequence of area to draw + * @param endSeq + * end sequence of area to draw + * @param offset + * vertical offset (used when called from wrapped alignment code) */ private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group, int startRes, int endRes, int startSeq, int endSeq, int offset) @@ -1377,22 +1264,17 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { // package into blocks of visible columns int screenY = 0; - int blockStart = startRes; - int blockEnd = endRes; + int blockStart; + int blockEnd; - for (int[] region : av.getAlignment().getHiddenColumns() - .getHiddenColumnsCopy()) + HiddenColumns hidden = av.getAlignment().getHiddenColumns(); + VisibleContigsIterator regions = hidden + .getVisContigsIterator(startRes, endRes + 1, true); + while (regions.hasNext()) { - int hideStart = region[0]; - int hideEnd = region[1]; - - if (hideStart <= blockStart) - { - blockStart += (hideEnd - hideStart) + 1; - continue; - } - - blockEnd = hideStart - 1; + int[] region = regions.next(); + blockEnd = region[1]; + blockStart = region[0]; g.translate(screenY * charWidth, 0); drawPartialGroupOutline(g, group, @@ -1400,30 +1282,20 @@ public class SeqCanvas extends JComponent implements ViewportListenerI g.translate(-screenY * charWidth, 0); screenY += blockEnd - blockStart + 1; - blockStart = hideEnd + 1; - - if (screenY > (endRes - startRes)) - { - // already rendered last block - break; - } - } - - if (screenY <= (endRes - startRes)) - { - // remaining visible region to render - blockEnd = blockStart + (endRes - startRes) - screenY; - g.translate(screenY * charWidth, 0); - drawPartialGroupOutline(g, group, - blockStart, blockEnd, startSeq, endSeq, offset); - - g.translate(-screenY * charWidth, 0); } } } - /* - * Draw the selection group as a separate image and overlay + /** + * Draws part of a selection group outline + * + * @param g + * @param group + * @param startRes + * @param endRes + * @param startSeq + * @param endSeq + * @param verticalOffset */ private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group, int startRes, int endRes, int startSeq, int endSeq, @@ -1681,9 +1553,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.hasHiddenColumns()) { firstVisibleColumn = alignment.getHiddenColumns() - .adjustForHiddenColumns(firstVisibleColumn); + .visibleToAbsoluteColumn(firstVisibleColumn); lastVisibleColumn = alignment.getHiddenColumns() - .adjustForHiddenColumns(lastVisibleColumn); + .visibleToAbsoluteColumn(lastVisibleColumn); } for (int seqNo = ranges.getStartSeq(); seqNo <= ranges @@ -1726,8 +1598,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.hasHiddenColumns()) { firstCol = alignment.getHiddenColumns() - .findColumnPosition(firstCol); - lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol); + .absoluteToVisibleColumn(firstCol); + lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol); } int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth(); int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight(); @@ -1784,14 +1656,30 @@ public class SeqCanvas extends JComponent implements ViewportListenerI scrollX = -range; } } - // Both scrolling and resizing change viewport ranges: scrolling changes - // both start and end points, but resize only changes end values. - // Here we only want to fastpaint on a scroll, with resize using a normal - // paint, so scroll events are identified as changes to the horizontal or - // vertical start value. - if (eventName.equals(ViewportRanges.STARTRES)) - { - if (av.getWrapAlignment()) + // Both scrolling and resizing change viewport ranges: scrolling changes + // both start and end points, but resize only changes end values. + // Here we only want to fastpaint on a scroll, with resize using a normal + // paint, so scroll events are identified as changes to the horizontal or + // vertical start value. + if (eventName.equals(ViewportRanges.STARTRES)) + { + if (av.getWrapAlignment()) + { + fastPaintWrapped(scrollX); + } + else + { + fastPaint(scrollX, 0); + } + } + else if (eventName.equals(ViewportRanges.STARTSEQ)) + { + // scroll + fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue()); + } + else if (eventName.equals(ViewportRanges.STARTRESANDSEQ)) + { + if (av.getWrapAlignment()) { fastPaintWrapped(scrollX); } @@ -1811,14 +1699,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { fastPaintWrapped(scrollX); } - else - { - fastPaint(scrollX, 0); - } - // bizarrely, we only need to scroll on the x value here as fastpaint - // copies the full height of the image anyway. Passing in the y value - // causes nasty repaint artefacts, which only disappear on a full - // repaint. } } @@ -1835,9 +1715,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { ViewportRanges ranges = av.getRanges(); - // if (Math.abs(scrollX) > ranges.getViewportWidth()) - // JAL-2836, 2836 temporarily removed wrapped fastpaint for release 2.10.3 - if (true) + if (Math.abs(scrollX) > ranges.getViewportWidth()) { /* * shift of more than one view width is @@ -2115,9 +1993,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.hasHiddenColumns()) { firstVisibleColumn = alignment.getHiddenColumns() - .adjustForHiddenColumns(firstVisibleColumn); + .visibleToAbsoluteColumn(firstVisibleColumn); lastVisibleColumn = alignment.getHiddenColumns() - .adjustForHiddenColumns(lastVisibleColumn); + .visibleToAbsoluteColumn(lastVisibleColumn); } int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1); @@ -2156,7 +2034,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.hasHiddenColumns()) { displayColumn = alignment.getHiddenColumns() - .findColumnPosition(displayColumn); + .absoluteToVisibleColumn(displayColumn); } /* diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 61cac46..8b2e7bc 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -59,7 +59,6 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -76,12 +75,11 @@ import javax.swing.ToolTipManager; public class SeqPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener, SequenceListener, SelectionListener - { - /** DOCUMENT ME!! */ + private static final int MAX_TOOLTIP_LENGTH = 300; + public SeqCanvas seqCanvas; - /** DOCUMENT ME!! */ public AlignmentPanel ap; /* @@ -148,35 +146,33 @@ public class SeqPanel extends JPanel SearchResultsI lastSearchResults; /** - * Creates a new SeqPanel object. + * Creates a new SeqPanel object * - * @param avp - * DOCUMENT ME! - * @param p - * DOCUMENT ME! + * @param viewport + * @param alignPanel */ - public SeqPanel(AlignViewport av, AlignmentPanel ap) + public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel) { linkImageURL = getClass().getResource("/images/link.gif"); seqARep = new SequenceAnnotationReport(linkImageURL.toString()); ToolTipManager.sharedInstance().registerComponent(this); ToolTipManager.sharedInstance().setInitialDelay(0); ToolTipManager.sharedInstance().setDismissDelay(10000); - this.av = av; + this.av = viewport; setBackground(Color.white); - seqCanvas = new SeqCanvas(ap); + seqCanvas = new SeqCanvas(alignPanel); setLayout(new BorderLayout()); add(seqCanvas, BorderLayout.CENTER); - this.ap = ap; + this.ap = alignPanel; - if (!av.isDataset()) + if (!viewport.isDataset()) { addMouseMotionListener(this); addMouseListener(this); addMouseWheelListener(this); - ssm = av.getStructureSelectionManager(); + ssm = viewport.getStructureSelectionManager(); ssm.addStructureViewerListener(this); ssm.addSelectionListener(this); } @@ -250,7 +246,7 @@ public class SeqPanel extends JPanel if (av.hasHiddenColumns()) { res = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(res); + .visibleToAbsoluteColumn(res); } return res; @@ -363,13 +359,25 @@ public class SeqPanel extends JPanel int original = seqCanvas.cursorX - dx; int maxWidth = av.getAlignment().getWidth(); - // TODO: once JAL-2759 is ready, change this loop to something more - // efficient - while (!hidden.isVisible(seqCanvas.cursorX) - && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0 - && dx != 0) + if (!hidden.isVisible(seqCanvas.cursorX)) { - seqCanvas.cursorX += dx; + int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx); + int[] region = hidden.getRegionWithEdgeAtRes(visx); + + if (region != null) // just in case + { + if (dx == 1) + { + // moving right + seqCanvas.cursorX = region[1] + 1; + } + else if (dx == -1) + { + // moving left + seqCanvas.cursorX = region[0] - 1; + } + } + seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX; } if (seqCanvas.cursorX >= maxWidth @@ -424,7 +432,7 @@ public class SeqPanel extends JPanel { // scrollToWrappedVisible expects x-value to have hidden cols subtracted int x = av.getAlignment().getHiddenColumns() - .findColumnPosition(seqCanvas.cursorX); + .absoluteToVisibleColumn(seqCanvas.cursorX); av.getRanges().scrollToWrappedVisible(x); } else @@ -838,7 +846,7 @@ public class SeqPanel extends JPanel List features = ap.getFeatureRenderer() .findFeaturesAtColumn(sequence, column + 1); seqARep.appendFeatures(tooltipText, pos, features, - this.ap.getSeqPanel().seqCanvas.fr.getMinMax()); + this.ap.getSeqPanel().seqCanvas.fr); } if (tooltipText.length() == 6) // { @@ -847,6 +855,11 @@ public class SeqPanel extends JPanel } else { + if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant + { + tooltipText.setLength(MAX_TOOLTIP_LENGTH); + tooltipText.append("..."); + } String textString = tooltipText.toString(); if (lastTooltip == null || !lastTooltip.equals(textString)) { @@ -1259,9 +1272,9 @@ public class SeqPanel extends JPanel { fixedColumns = true; int y1 = av.getAlignment().getHiddenColumns() - .getHiddenBoundaryLeft(startres); + .getNextHiddenBoundary(true, startres); int y2 = av.getAlignment().getHiddenColumns() - .getHiddenBoundaryRight(startres); + .getNextHiddenBoundary(false, startres); if ((insertGap && startres > y1 && lastres < y1) || (!insertGap && startres < y2 && lastres > y2)) @@ -1337,7 +1350,8 @@ public class SeqPanel extends JPanel if (sg.getSize() == av.getAlignment().getHeight()) { if ((av.hasHiddenColumns() && startres < av.getAlignment() - .getHiddenColumns().getHiddenBoundaryRight(startres))) + .getHiddenColumns() + .getNextHiddenBoundary(false, startres))) { endEditing(); return; @@ -1656,7 +1670,8 @@ public class SeqPanel extends JPanel public void mouseWheelMoved(MouseWheelEvent e) { e.consume(); - if (e.getWheelRotation() > 0) + double wheelRotation = e.getPreciseWheelRotation(); + if (wheelRotation > 0) { if (e.isShiftDown()) { @@ -1668,7 +1683,7 @@ public class SeqPanel extends JPanel av.getRanges().scrollUp(false); } } - else + else if (wheelRotation < 0) { if (e.isShiftDown()) { @@ -1825,21 +1840,10 @@ public class SeqPanel extends JPanel final int column = findColumn(evt); final int seq = findSeq(evt); SequenceI sequence = av.getAlignment().getSequenceAt(seq); - List allFeatures = ap.getFeatureRenderer() + List features = ap.getFeatureRenderer() .findFeaturesAtColumn(sequence, column + 1); - List links = new ArrayList<>(); - for (SequenceFeature sf : allFeatures) - { - if (sf.links != null) - { - for (String link : sf.links) - { - links.add(link); - } - } - } - PopupMenu pop = new PopupMenu(ap, null, links); + PopupMenu pop = new PopupMenu(ap, null, features); pop.show(this, evt.getX(), evt.getY()); } @@ -2288,4 +2292,13 @@ public class SeqPanel extends JPanel return true; } + + /** + * + * @return null or last search results handled by this panel + */ + public SearchResultsI getLastSearchResults() + { + return lastSearchResults; + } } diff --git a/src/jalview/gui/SequenceFetcher.java b/src/jalview/gui/SequenceFetcher.java index 8d46792..8754fbb 100755 --- a/src/jalview/gui/SequenceFetcher.java +++ b/src/jalview/gui/SequenceFetcher.java @@ -34,7 +34,6 @@ import jalview.io.gff.SequenceOntologyI; import jalview.util.DBRefUtils; import jalview.util.MessageManager; import jalview.util.Platform; -import jalview.ws.dbsources.das.api.DasSourceRegistryI; import jalview.ws.seqfetcher.DbSourceProxy; import java.awt.BorderLayout; @@ -111,10 +110,6 @@ public class SequenceFetcher extends JPanel implements Runnable private static jalview.ws.SequenceFetcher sfetch = null; - private static long lastDasSourceRegistry = -3; - - private static DasSourceRegistryI dasRegistry = null; - private static boolean _initingFetcher = false; private static Thread initingThread = null; @@ -165,11 +160,7 @@ public class SequenceFetcher extends JPanel implements Runnable Thread.currentThread().hashCode()); } } - if (sfetch == null || dasRegistry != Cache.getDasSourceRegistry() - || lastDasSourceRegistry != (Cache.getDasSourceRegistry() - .getDasRegistryURL() - + Cache.getDasSourceRegistry().getLocalSourceString()) - .hashCode()) + if (sfetch == null) { _initingFetcher = true; initingThread = Thread.currentThread(); @@ -183,16 +174,12 @@ public class SequenceFetcher extends JPanel implements Runnable "status.init_sequence_database_fetchers"), Thread.currentThread().hashCode()); } - dasRegistry = Cache.getDasSourceRegistry(); - dasRegistry.refreshSources(); jalview.ws.SequenceFetcher sf = new jalview.ws.SequenceFetcher(); if (guiWindow != null) { guiWindow.setProgressBar(null, Thread.currentThread().hashCode()); } - lastDasSourceRegistry = (dasRegistry.getDasRegistryURL() - + dasRegistry.getLocalSourceString()).hashCode(); sfetch = sf; _initingFetcher = false; initingThread = null; @@ -273,7 +260,7 @@ public class SequenceFetcher extends JPanel implements Runnable return Collections.emptyList(); } } - sf.newAlframes = new ArrayList(); + sf.newAlframes = new ArrayList<>(); sf.run(); return sf.newAlframes; } @@ -564,15 +551,10 @@ public class SequenceFetcher extends JPanel implements Runnable dbeg.setText(MessageManager.formatMessage("label.example_query_param", new String[] { eq })); + // TODO this should be a property of the SequenceFetcher whether commas are and + // colons are allowed in the IDs... + boolean enablePunct = !(eq != null && eq.indexOf(",") > -1); - for (DbSourceProxy dbs : database.getSelectedSources()) - { - if (dbs instanceof jalview.ws.dbsources.das.datamodel.DasSequenceSource) - { - enablePunct = false; - break; - } - } replacePunctuation.setEnabled(enablePunct); } catch (Exception ex) @@ -674,10 +656,10 @@ public class SequenceFetcher extends JPanel implements Runnable // TODO: Refactor to GUI independent code and write tests. // indicate if successive sources should be merged into one alignment. boolean addToLast = false; - List aresultq = new ArrayList(); - List presultTitle = new ArrayList(); - List presult = new ArrayList(); - List aresult = new ArrayList(); + List aresultq = new ArrayList<>(); + List presultTitle = new ArrayList<>(); + List presult = new ArrayList<>(); + List aresult = new ArrayList<>(); Iterator proxies = database.getSelectedSources() .iterator(); String[] qries; @@ -695,7 +677,7 @@ public class SequenceFetcher extends JPanel implements Runnable nqueries = nextFetch.size(); // save the remaining queries in the original array qries = nextFetch.toArray(new String[nqueries]); - nextFetch = new ArrayList(); + nextFetch = new ArrayList<>(); } DbSourceProxy proxy = proxies.next(); @@ -861,7 +843,7 @@ public class SequenceFetcher extends JPanel implements Runnable List aresult, List nextFetch) throws Exception { StringBuilder multiacc = new StringBuilder(); - List tosend = new ArrayList(); + List tosend = new ArrayList<>(); while (accessions.hasNext()) { String nel = accessions.next(); diff --git a/src/jalview/gui/StructureChooser.java b/src/jalview/gui/StructureChooser.java index 7c386f1..e18d6af 100644 --- a/src/jalview/gui/StructureChooser.java +++ b/src/jalview/gui/StructureChooser.java @@ -21,6 +21,8 @@ package jalview.gui; +import jalview.api.structures.JalviewStructureDisplayI; +import jalview.bin.Cache; import jalview.bin.Jalview; import jalview.datamodel.DBRefEntry; import jalview.datamodel.DBRefSource; @@ -53,6 +55,8 @@ import java.util.Vector; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; /** @@ -65,6 +69,8 @@ import javax.swing.table.AbstractTableModel; public class StructureChooser extends GStructureChooser implements IProgressIndicator { + private static final String AUTOSUPERIMPOSE = "AUTOSUPERIMPOSE"; + private static int MAX_QLENGTH = 7820; private SequenceI selectedSequence; @@ -85,6 +91,8 @@ public class StructureChooser extends GStructureChooser private boolean cachedPDBExists; + private static StructureViewer lastTargetedView = null; + public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq, AlignmentPanel ap) { @@ -98,13 +106,15 @@ public class StructureChooser extends GStructureChooser /** * Initializes parameters used by the Structure Chooser Panel */ - public void init() + protected void init() { if (!Jalview.isHeadlessMode()) { progressBar = new ProgressBar(this.statusPanel, this.statusBar); } + chk_superpose.setSelected(Cache.getDefault(AUTOSUPERIMPOSE, true)); + // ensure a filter option is in force for search populateFilterComboBox(true, cachedPDBExists); Thread discoverPDBStructuresThread = new Thread(new Runnable() @@ -122,6 +132,7 @@ public class StructureChooser extends GStructureChooser fetchStructuresMetaData(); // revise filter options if no results were found populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists); + discoverStructureViews(); updateProgressIndicator(null, startTime); mainFrame.setVisible(true); updateCurrentView(); @@ -131,6 +142,59 @@ public class StructureChooser extends GStructureChooser } /** + * Builds a drop-down choice list of existing structure viewers to which new + * structures may be added. If this list is empty then it, and the 'Add' + * button, are hidden. + */ + private void discoverStructureViews() + { + if (Desktop.instance != null) + { + targetView.removeAllItems(); + if (lastTargetedView != null && !lastTargetedView.isVisible()) + { + lastTargetedView = null; + } + int linkedViewsAt = 0; + for (StructureViewerBase view : Desktop.instance + .getStructureViewers(null, null)) + { + StructureViewer viewHandler = (lastTargetedView != null + && lastTargetedView.sview == view) ? lastTargetedView + : StructureViewer.reconfigure(view); + + if (view.isLinkedWith(ap)) + { + targetView.insertItemAt(viewHandler, + linkedViewsAt++); + } + else + { + targetView.addItem(viewHandler); + } + } + + /* + * show option to Add to viewer if at least 1 viewer found + */ + targetView.setVisible(false); + if (targetView.getItemCount() > 0) + { + targetView.setVisible(true); + if (lastTargetedView != null) + { + targetView.setSelectedItem(lastTargetedView); + } + else + { + targetView.setSelectedIndex(0); + } + } + btn_add.setVisible(targetView.isVisible()); + } + } + + /** * Updates the progress indicator with the specified message * * @param message @@ -138,7 +202,7 @@ public class StructureChooser extends GStructureChooser * @param id * unique handle for this indicator */ - public void updateProgressIndicator(String message, long id) + protected void updateProgressIndicator(String message, long id) { if (progressIndicator != null) { @@ -150,7 +214,7 @@ public class StructureChooser extends GStructureChooser * Retrieve meta-data for all the structure(s) for a given sequence(s) in a * selection group */ - public void fetchStructuresMetaData() + void fetchStructuresMetaData() { long startTime = System.currentTimeMillis(); pdbRestCleint = PDBFTSRestClient.getInstance(); @@ -221,7 +285,7 @@ public class StructureChooser extends GStructureChooser } } - public void loadLocalCachedPDBEntries() + protected void loadLocalCachedPDBEntries() { ArrayList entries = new ArrayList<>(); for (SequenceI seq : selectedSequences) @@ -252,7 +316,7 @@ public class StructureChooser extends GStructureChooser * @return the built query string */ - public static String buildQuery(SequenceI seq) + static String buildQuery(SequenceI seq) { boolean isPDBRefsFound = false; boolean isUniProtRefsFound = false; @@ -354,7 +418,7 @@ public class StructureChooser extends GStructureChooser * @param seqName * @return */ - public static boolean isValidSeqName(String seqName) + static boolean isValidSeqName(String seqName) { // System.out.println("seqName : " + seqName); String ignoreList = "pdb,uniprot,swiss-prot"; @@ -377,7 +441,7 @@ public class StructureChooser extends GStructureChooser return true; } - public static String getDBRefId(DBRefEntry dbRef) + static String getDBRefId(DBRefEntry dbRef) { String ref = dbRef.getAccessionId().replaceAll("GO:", ""); return ref; @@ -389,7 +453,7 @@ public class StructureChooser extends GStructureChooser * @param fieldToFilterBy * the field to filter by */ - public void filterResultSet(final String fieldToFilterBy) + void filterResultSet(final String fieldToFilterBy) { Thread filterThread = new Thread(new Runnable() { @@ -499,7 +563,7 @@ public class StructureChooser extends GStructureChooser * Handles action event for btn_pdbFromFile */ @Override - public void pdbFromFile_actionPerformed() + protected void pdbFromFile_actionPerformed() { jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser( jalview.bin.Cache.getProperty("LAST_DIRECTORY")); @@ -525,7 +589,7 @@ public class StructureChooser extends GStructureChooser * structures */ protected void populateFilterComboBox(boolean haveData, - boolean cachedPDBExists) + boolean cachedPDBExist) { /* * temporarily suspend the change listener behaviour @@ -535,25 +599,33 @@ public class StructureChooser extends GStructureChooser cmb_filterOption.removeAllItems(); if (haveData) { - cmb_filterOption.addItem(new FilterOption("Best Quality", + cmb_filterOption.addItem(new FilterOption( + MessageManager.getString("label.best_quality"), "overall_quality", VIEWS_FILTER, false)); - cmb_filterOption.addItem(new FilterOption("Best Resolution", + cmb_filterOption.addItem(new FilterOption( + MessageManager.getString("label.best_resolution"), "resolution", VIEWS_FILTER, false)); - cmb_filterOption.addItem(new FilterOption("Most Protein Chain", + cmb_filterOption.addItem(new FilterOption( + MessageManager.getString("label.most_protein_chain"), "number_of_protein_chains", VIEWS_FILTER, false)); - cmb_filterOption.addItem(new FilterOption("Most Bound Molecules", + cmb_filterOption.addItem(new FilterOption( + MessageManager.getString("label.most_bound_molecules"), "number_of_bound_molecules", VIEWS_FILTER, false)); - cmb_filterOption.addItem(new FilterOption("Most Polymer Residues", + cmb_filterOption.addItem(new FilterOption( + MessageManager.getString("label.most_polymer_residues"), "number_of_polymer_residues", VIEWS_FILTER, true)); } cmb_filterOption.addItem( - new FilterOption("Enter PDB Id", "-", VIEWS_ENTER_ID, false)); + new FilterOption(MessageManager.getString("label.enter_pdb_id"), + "-", VIEWS_ENTER_ID, false)); cmb_filterOption.addItem( - new FilterOption("From File", "-", VIEWS_FROM_FILE, false)); + new FilterOption(MessageManager.getString("label.from_file"), + "-", VIEWS_FROM_FILE, false)); - if (cachedPDBExists) + if (cachedPDBExist) { - FilterOption cachedOption = new FilterOption("Cached Structures", + FilterOption cachedOption = new FilterOption( + MessageManager.getString("label.cached_structures"), "-", VIEWS_LOCAL_PDB, false); cmb_filterOption.addItem(cachedOption); cmb_filterOption.setSelectedItem(cachedOption); @@ -592,28 +664,37 @@ public class StructureChooser extends GStructureChooser } /** - * Validates user selection and activates the view button if all parameters - * are correct + * Validates user selection and enables the 'Add' and 'New View' buttons if + * all parameters are correct (the Add button will only be visible if there is + * at least one existing structure viewer open). This basically means at least + * one structure selected and no error messages. + *

            + * The 'Superpose Structures' option is enabled if either more than one + * structure is selected, or the 'Add' to existing view option is enabled, and + * disabled if the only option is to open a new view of a single structure. */ @Override - public void validateSelections() + protected void validateSelections() { FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption .getSelectedItem()); - btn_view.setEnabled(false); + btn_add.setEnabled(false); String currentView = selectedFilterOpt.getView(); + int selectedCount = 0; if (currentView == VIEWS_FILTER) { - if (getResultTable().getSelectedRows().length > 0) + selectedCount = getResultTable().getSelectedRows().length; + if (selectedCount > 0) { - btn_view.setEnabled(true); + btn_add.setEnabled(true); } } else if (currentView == VIEWS_LOCAL_PDB) { - if (tbl_local_pdb.getSelectedRows().length > 0) + selectedCount = tbl_local_pdb.getSelectedRows().length; + if (selectedCount > 0) { - btn_view.setEnabled(true); + btn_add.setEnabled(true); } } else if (currentView == VIEWS_ENTER_ID) @@ -624,12 +705,21 @@ public class StructureChooser extends GStructureChooser { validateAssociationFromFile(); } + + btn_newView.setEnabled(btn_add.isEnabled()); + + /* + * enable 'Superpose' option if more than one structure is selected, + * or there are view(s) available to add structure(s) to + */ + chk_superpose + .setEnabled(selectedCount > 1 || targetView.getItemCount() > 0); } /** * Validates inputs from the Manual PDB entry panel */ - public void validateAssociationEnterPdb() + protected void validateAssociationEnterPdb() { AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel .getCmb_assSeq().getSelectedItem(); @@ -655,7 +745,7 @@ public class StructureChooser extends GStructureChooser txt_search.setEnabled(true); if (isValidPBDEntry) { - btn_view.setEnabled(true); + btn_add.setEnabled(true); lbl_pdbManualFetchStatus.setToolTipText(""); lbl_pdbManualFetchStatus.setIcon(goodImage); } @@ -670,7 +760,7 @@ public class StructureChooser extends GStructureChooser /** * Validates inputs for the manual PDB file selection options */ - public void validateAssociationFromFile() + protected void validateAssociationFromFile() { AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel .getCmb_assSeq().getSelectedItem(); @@ -681,7 +771,7 @@ public class StructureChooser extends GStructureChooser btn_pdbFromFile.setEnabled(true); if (selectedPdbFileName != null && selectedPdbFileName.length() > 0) { - btn_view.setEnabled(true); + btn_add.setEnabled(true); lbl_fromFileStatus.setIcon(goodImage); } } @@ -693,7 +783,7 @@ public class StructureChooser extends GStructureChooser } @Override - public void cmbAssSeqStateChanged() + protected void cmbAssSeqStateChanged() { validateSelections(); } @@ -720,16 +810,76 @@ public class StructureChooser extends GStructureChooser } /** - * Handles action event for btn_ok + * select structures for viewing by their PDB IDs + * + * @param pdbids + * @return true if structures were found and marked as selected + */ + public boolean selectStructure(String... pdbids) + { + boolean found = false; + + FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption + .getSelectedItem()); + String currentView = selectedFilterOpt.getView(); + JTable restable = (currentView == VIEWS_FILTER) ? getResultTable() + : (currentView == VIEWS_LOCAL_PDB) ? tbl_local_pdb : null; + + if (restable == null) + { + // can't select (enter PDB ID, or load file - need to also select which + // sequence to associate with) + return false; + } + + int pdbIdColIndex = restable.getColumn("PDB Id").getModelIndex(); + for (int r = 0; r < restable.getRowCount(); r++) + { + for (int p = 0; p < pdbids.length; p++) + { + if (String.valueOf(restable.getValueAt(r, pdbIdColIndex)) + .equalsIgnoreCase(pdbids[p])) + { + restable.setRowSelectionInterval(r, r); + found = true; + } + } + } + return found; + } + + /** + * Handles the 'New View' action */ @Override - public void ok_ActionPerformed() + protected void newView_ActionPerformed() { + targetView.setSelectedItem(null); + showStructures(false); + } + + /** + * Handles the 'Add to existing viewer' action + */ + @Override + protected void add_ActionPerformed() + { + showStructures(false); + } + + /** + * structure viewer opened by this dialog, or null + */ + private StructureViewer sViewer = null; + + public void showStructures(boolean waitUntilFinished) + { + final StructureSelectionManager ssm = ap.getStructureSelectionManager(); final int preferredHeight = pnl_filter.getHeight(); - new Thread(new Runnable() + Runnable viewStruc = new Runnable() { @Override public void run() @@ -737,21 +887,24 @@ public class StructureChooser extends GStructureChooser FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption .getSelectedItem()); String currentView = selectedFilterOpt.getView(); + JTable restable = (currentView == VIEWS_FILTER) ? getResultTable() + : tbl_local_pdb; + if (currentView == VIEWS_FILTER) { - int pdbIdColIndex = getResultTable().getColumn("PDB Id") + int pdbIdColIndex = restable.getColumn("PDB Id") .getModelIndex(); - int refSeqColIndex = getResultTable().getColumn("Ref Sequence") + int refSeqColIndex = restable.getColumn("Ref Sequence") .getModelIndex(); - int[] selectedRows = getResultTable().getSelectedRows(); + int[] selectedRows = restable.getSelectedRows(); PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length]; int count = 0; List selectedSeqsToView = new ArrayList<>(); for (int row : selectedRows) { - String pdbIdStr = getResultTable() + String pdbIdStr = restable .getValueAt(row, pdbIdColIndex).toString(); - SequenceI selectedSeq = (SequenceI) getResultTable() + SequenceI selectedSeq = (SequenceI) restable .getValueAt(row, refSeqColIndex); selectedSeqsToView.add(selectedSeq); PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr); @@ -772,7 +925,8 @@ public class StructureChooser extends GStructureChooser } SequenceI[] selectedSeqs = selectedSeqsToView .toArray(new SequenceI[selectedSeqsToView.size()]); - launchStructureViewer(ssm, pdbEntriesToView, ap, selectedSeqs); + sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap, + selectedSeqs); } else if (currentView == VIEWS_LOCAL_PDB) { @@ -795,7 +949,8 @@ public class StructureChooser extends GStructureChooser } SequenceI[] selectedSeqs = selectedSeqsToView .toArray(new SequenceI[selectedSeqsToView.size()]); - launchStructureViewer(ssm, pdbEntriesToView, ap, selectedSeqs); + sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap, + selectedSeqs); } else if (currentView == VIEWS_ENTER_ID) { @@ -824,7 +979,7 @@ public class StructureChooser extends GStructureChooser } PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry }; - launchStructureViewer(ssm, pdbEntriesToView, ap, + sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap, new SequenceI[] { selectedSequence }); } @@ -841,14 +996,40 @@ public class StructureChooser extends GStructureChooser DataSourceType.FILE, selectedSequence, true, Desktop.instance); - launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap, + sViewer = launchStructureViewer( + ssm, new PDBEntry[] + { fileEntry }, ap, new SequenceI[] { selectedSequence }); } - closeAction(preferredHeight); - mainFrame.dispose(); + SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + closeAction(preferredHeight); + mainFrame.dispose(); + } + }); } - }).start(); + }; + Thread runner = new Thread(viewStruc); + runner.start(); + if (waitUntilFinished) + { + while (sViewer == null ? runner.isAlive() + : (sViewer.sview == null ? true + : !sViewer.sview.hasMapping())) + { + try + { + Thread.sleep(300); + } catch (InterruptedException ie) + { + + } + } + } } private PDBEntry getFindEntry(String id, Vector pdbEntries) @@ -866,16 +1047,49 @@ public class StructureChooser extends GStructureChooser return foundEntry; } - private void launchStructureViewer(StructureSelectionManager ssm, + /** + * Answers a structure viewer (new or existing) configured to superimpose + * added structures or not according to the user's choice + * + * @param ssm + * @return + */ + StructureViewer getTargetedStructureViewer( + StructureSelectionManager ssm) + { + Object sv = targetView.getSelectedItem(); + + return sv == null ? new StructureViewer(ssm) : (StructureViewer) sv; + } + + /** + * Adds PDB structures to a new or existing structure viewer + * + * @param ssm + * @param pdbEntriesToView + * @param alignPanel + * @param sequences + * @return + */ + private StructureViewer launchStructureViewer( + StructureSelectionManager ssm, final PDBEntry[] pdbEntriesToView, final AlignmentPanel alignPanel, SequenceI[] sequences) { long progressId = sequences.hashCode(); setProgressBar(MessageManager .getString("status.launching_3d_structure_viewer"), progressId); - final StructureViewer sViewer = new StructureViewer(ssm); - setProgressBar(null, progressId); + final StructureViewer theViewer = getTargetedStructureViewer(ssm); + boolean superimpose = chk_superpose.isSelected(); + theViewer.setSuperpose(superimpose); + /* + * remember user's choice of superimpose or not + */ + Cache.setProperty(AUTOSUPERIMPOSE, + Boolean.valueOf(superimpose).toString()); + + setProgressBar(null, progressId); if (SiftsSettings.isMapWithSifts()) { List seqsWithoutSourceDBRef = new ArrayList<>(); @@ -900,7 +1114,7 @@ public class StructureChooser extends GStructureChooser } } } - if (seq.getPrimaryDBRefs().size() == 0) + if (seq.getPrimaryDBRefs().isEmpty()) { seqsWithoutSourceDBRef.add(seq); continue; @@ -912,13 +1126,8 @@ public class StructureChooser extends GStructureChooser setProgressBar(MessageManager.formatMessage( "status.fetching_dbrefs_for_sequences_without_valid_refs", y), progressId); - SequenceI[] seqWithoutSrcDBRef = new SequenceI[y]; - int x = 0; - for (SequenceI fSeq : seqsWithoutSourceDBRef) - { - seqWithoutSrcDBRef[x++] = fSeq; - } - + SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef + .toArray(new SequenceI[y]); DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef); dbRefFetcher.fetchDBRefs(true); @@ -930,16 +1139,19 @@ public class StructureChooser extends GStructureChooser setProgressBar(MessageManager.getString( "status.fetching_3d_structures_for_selected_entries"), progressId); - sViewer.viewStructures(pdbEntriesToView, sequences, alignPanel); + theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel); } else { setProgressBar(MessageManager.formatMessage( "status.fetching_3d_structures_for", pdbEntriesToView[0].getId()),progressId); - sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel); + theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel); } setProgressBar(null, progressId); + // remember the last viewer we used... + lastTargetedView = theViewer; + return theViewer; } /** @@ -947,7 +1159,7 @@ public class StructureChooser extends GStructureChooser * a unique sequence when more than one sequence selection is made. */ @Override - public void populateCmbAssociateSeqOptions( + protected void populateCmbAssociateSeqOptions( JComboBox cmb_assSeq, JLabel lbl_associateSeq) { @@ -972,17 +1184,12 @@ public class StructureChooser extends GStructureChooser } } - public boolean isStructuresDiscovered() + protected boolean isStructuresDiscovered() { return discoveredStructuresSet != null && !discoveredStructuresSet.isEmpty(); } - public Collection getDiscoveredStructuresSet() - { - return discoveredStructuresSet; - } - @Override protected void txt_search_ActionPerformed() { @@ -1032,7 +1239,7 @@ public class StructureChooser extends GStructureChooser } @Override - public void tabRefresh() + protected void tabRefresh() { if (selectedSequences != null) { @@ -1170,4 +1377,9 @@ public class StructureChooser extends GStructureChooser { return progressBar.operationInProgress(); } + + public JalviewStructureDisplayI getOpenedStructureViewer() + { + return sViewer == null ? null : sViewer.sview; + } } diff --git a/src/jalview/gui/StructureViewer.java b/src/jalview/gui/StructureViewer.java index fb37b77..0c8354b 100644 --- a/src/jalview/gui/StructureViewer.java +++ b/src/jalview/gui/StructureViewer.java @@ -49,6 +49,11 @@ public class StructureViewer StructureSelectionManager ssm; + /** + * decide if new structures are aligned to existing ones + */ + private boolean superposeAdded = true; + public enum ViewerType { JMOL, CHIMERA @@ -64,6 +69,27 @@ public class StructureViewer ssm = structureSelectionManager; } + /** + * Factory to create a proxy for modifying existing structure viewer + * + */ + public static StructureViewer reconfigure( + JalviewStructureDisplayI display) + { + StructureViewer sv = new StructureViewer(display.getBinding().getSsm()); + sv.sview = display; + return sv; + } + + @Override + public String toString() + { + if (sview != null) + { + return sview.toString(); + } + return "New View"; + } public ViewerType getViewerType() { String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY, @@ -104,14 +130,40 @@ public class StructureViewer new PDBEntry[seqsForPdbs.size()]); SequenceI[][] theSeqs = seqsForPdbs.values().toArray( new SequenceI[seqsForPdbs.size()][]); - JalviewStructureDisplayI sview = null; + if (sview != null) + { + sview.setAlignAddedStructures(superposeAdded); + new Thread(new Runnable() + { + @Override + public void run() + { + + for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++) + { + PDBEntry pdb = pdbsForFile[pdbep]; + if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap, + pdb.getId())) + { + sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap, + pdb.getId()); + } + } + + sview.updateTitleAndMenus(); + } + }).start(); + return sview; + } + if (viewerType.equals(ViewerType.JMOL)) { - sview = new AppJmol(ap, pdbsForFile, theSeqs); + sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs); } else if (viewerType.equals(ViewerType.CHIMERA)) { - sview = new ChimeraViewFrame(pdbsForFile, theSeqs, ap); + sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs, + ap); } else { @@ -203,7 +255,7 @@ public class StructureViewer private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs, SequenceI[] seqsForPdbs, AlignmentPanel ap) { - List seqs = new ArrayList(); + List seqs = new ArrayList<>(); if (pdbs == null || pdbs.length == 0) { return null; @@ -227,11 +279,24 @@ public class StructureViewer ap); } + JalviewStructureDisplayI sview = null; + public JalviewStructureDisplayI viewStructures(PDBEntry pdb, SequenceI[] seqsForPdb, AlignmentPanel ap) { + if (sview != null) + { + sview.setAlignAddedStructures(superposeAdded); + String pdbId = pdb.getId(); + if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId)) + { + sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId); + } + sview.updateTitleAndMenus(); + sview.raiseViewer(); + return sview; + } ViewerType viewerType = getViewerType(); - JalviewStructureDisplayI sview = null; if (viewerType.equals(ViewerType.JMOL)) { sview = new AppJmol(pdb, seqsForPdb, null, ap); @@ -270,7 +335,6 @@ public class StructureViewer final boolean usetoColourbyseq = viewerData.isColourWithAlignPanel(); final boolean viewerColouring = viewerData.isColourByViewer(); - JalviewStructureDisplayI sview = null; switch (type) { case JMOL: @@ -287,4 +351,41 @@ public class StructureViewer return sview; } + public boolean isBusy() + { + if (sview != null) + { + if (!sview.hasMapping()) + { + return true; + } + } + return false; + } + + /** + * + * @param pDBid + * @return true if view is already showing PDBid + */ + public boolean hasPdbId(String pDBid) + { + if (sview == null) + { + return false; + } + + return sview.getBinding().hasPdbId(pDBid); + } + + public boolean isVisible() + { + return sview != null && sview.isVisible(); + } + + public void setSuperpose(boolean alignAddedStructures) + { + superposeAdded = alignAddedStructures; + } + } diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java index 31c20ed..72b0bcc 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -20,6 +20,7 @@ */ package jalview.gui; +import jalview.api.AlignmentViewPanel; import jalview.bin.Cache; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; @@ -34,6 +35,7 @@ import jalview.io.JalviewFileView; import jalview.jbgui.GStructureViewer; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemes; +import jalview.structure.StructureMapping; import jalview.structures.models.AAStructureBindingModel; import jalview.util.MessageManager; @@ -102,9 +104,9 @@ public abstract class StructureViewerBase extends GStructureViewer protected boolean alignAddedStructures = false; - protected boolean _started = false; + protected volatile boolean _started = false; - protected boolean addingStructures = false; + protected volatile boolean addingStructures = false; protected Thread worker = null; @@ -113,6 +115,13 @@ public abstract class StructureViewerBase extends GStructureViewer protected JMenu viewSelectionMenu; /** + * set after sequence colouring has been applied for this structure viewer. + * used to determine if the final sequence/structure mapping has been + * determined + */ + protected volatile boolean seqColoursApplied = false; + + /** * Default constructor */ public StructureViewerBase() @@ -121,6 +130,26 @@ public abstract class StructureViewerBase extends GStructureViewer } /** + * @return true if added structures should be aligned to existing one(s) + */ + @Override + public boolean isAlignAddedStructures() + { + return alignAddedStructures; + } + + /** + * + * @param true + * if added structures should be aligned to existing one(s) + */ + @Override + public void setAlignAddedStructures(boolean alignAdded) + { + alignAddedStructures = alignAdded; + } + + /** * * @param ap2 * @return true if this Jmol instance is linked with the given alignPanel @@ -326,7 +355,7 @@ public abstract class StructureViewerBase extends GStructureViewer */ protected void addStructure(final PDBEntry pdbentry, final SequenceI[] seqs, final String[] chains, - final boolean align, final IProgressIndicator alignFrame) + final IProgressIndicator alignFrame) { if (pdbentry.getFile() == null) { @@ -350,7 +379,7 @@ public abstract class StructureViewerBase extends GStructureViewer } } // and call ourselves again. - addStructure(pdbentry, seqs, chains, align, alignFrame); + addStructure(pdbentry, seqs, chains, alignFrame); } }).start(); return; @@ -362,87 +391,42 @@ public abstract class StructureViewerBase extends GStructureViewer { seqs }, new String[][] { chains }); addingStructures = true; _started = false; - alignAddedStructures = align; worker = new Thread(this); worker.start(); return; } - /** - * Presents a dialog with the option to add an align a structure to an - * existing structure view - * - * @param pdbId - * @param view - * @return YES, NO or CANCEL JvOptionPane code - */ - protected int chooseAlignStructureToViewer(String pdbId, - StructureViewerBase view) - { - int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop, - MessageManager.formatMessage("label.add_pdbentry_to_view", - new Object[] - { pdbId, view.getTitle() }), - MessageManager - .getString("label.align_to_existing_structure_view"), - JvOptionPane.YES_NO_CANCEL_OPTION); - return option; - } - protected boolean hasPdbId(String pdbId) { return getBinding().hasPdbId(pdbId); } - protected abstract List getViewersFor( - AlignmentPanel alp); - /** - * Check for any existing views involving this alignment and give user the - * option to add and align this molecule to one of them - * - * @param pdbentry - * @param seq - * @param chains - * @param apanel - * @param pdbId - * @return true if user adds to a view, or cancels entirely, else false + * Returns a list of any viewer of the instantiated type. The list is + * restricted to those linked to the given alignment panel if it is not null. */ - protected boolean addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq, - String[] chains, final AlignmentPanel apanel, String pdbId) + protected List getViewersFor(AlignmentPanel alp) { - for (StructureViewerBase view : getViewersFor(apanel)) - { - // TODO: highlight the view somehow - /* - * JAL-1742 exclude view with this structure already mapped (don't offer - * to align chain B to chain A of the same structure) - */ - if (view.hasPdbId(pdbId)) - { - continue; - } - int option = chooseAlignStructureToViewer(pdbId, view); - if (option == JvOptionPane.CANCEL_OPTION) - { - return true; - } - else if (option == JvOptionPane.YES_OPTION) - { - view.useAlignmentPanelForSuperposition(apanel); - view.addStructure(pdbentry, seq, chains, true, apanel.alignFrame); - return true; - } - else - { - // NO_OPTION - offer the next viewer if any - } - } + return Desktop.instance.getStructureViewers(alp, this.getClass()); + } + @Override + public void addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq, + String[] chains, final AlignmentViewPanel apanel, String pdbId) + { /* - * nothing offered and selected + * JAL-1742 exclude view with this structure already mapped (don't offer + * to align chain B to chain A of the same structure); code may defend + * against this possibility before we reach here */ - return false; + if (hasPdbId(pdbId)) + { + return; + } + AlignmentPanel alignPanel = (AlignmentPanel) apanel; // Implementation error if this + // cast fails + useAlignmentPanelForSuperposition(alignPanel); + addStructure(pdbentry, seq, chains, alignPanel.alignFrame); } /** @@ -454,9 +438,12 @@ public abstract class StructureViewerBase extends GStructureViewer * @param apanel * @param pdbFilename */ - protected void addSequenceMappingsToStructure(SequenceI[] seq, - String[] chains, final AlignmentPanel apanel, String pdbFilename) + public void addSequenceMappingsToStructure(SequenceI[] seq, + String[] chains, final AlignmentViewPanel alpanel, + String pdbFilename) { + AlignmentPanel apanel = (AlignmentPanel) alpanel; + // TODO : Fix multiple seq to one chain issue here. /* * create the mappings @@ -503,47 +490,20 @@ public abstract class StructureViewerBase extends GStructureViewer } } - /** - * Check if the PDB file is already loaded, if so offer to add it to the - * existing viewer - * - * @param seq - * @param chains - * @param apanel - * @param pdbId - * @return true if the user chooses to add to a viewer, or to cancel entirely - */ - protected boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains, - final AlignmentPanel apanel, String pdbId) + @Override + public boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains, + final AlignmentViewPanel apanel, String pdbId) { - boolean finished = false; String alreadyMapped = apanel.getStructureSelectionManager() .alreadyMappedToFile(pdbId); - if (alreadyMapped != null) + if (alreadyMapped == null) { - /* - * the PDB file is already loaded - */ - int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop, - MessageManager.formatMessage( - "label.pdb_entry_is_already_displayed", new Object[] - { pdbId }), - MessageManager.formatMessage( - "label.map_sequences_to_visible_window", new Object[] - { pdbId }), - JvOptionPane.YES_NO_CANCEL_OPTION); - if (option == JvOptionPane.CANCEL_OPTION) - { - finished = true; - } - else if (option == JvOptionPane.YES_OPTION) - { - addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped); - finished = true; - } + return false; } - return finished; + + addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped); + return true; } void setChainMenuItems(List chainNames) @@ -823,11 +783,11 @@ public abstract class StructureViewerBase extends GStructureViewer int[] alm = new int[_alignwith.size()]; int a = 0; - for (AlignmentPanel ap : _alignwith) + for (AlignmentPanel alignPanel : _alignwith) { - als[a] = ap.av.getAlignment(); + als[a] = alignPanel.av.getAlignment(); alm[a] = -1; - alc[a++] = ap.av.getAlignment().getHiddenColumns(); + alc[a++] = alignPanel.av.getAlignment().getHiddenColumns(); } reply = getBinding().superposeStructures(als, alm, alc); if (reply != null) @@ -839,9 +799,9 @@ public abstract class StructureViewerBase extends GStructureViewer } catch (Exception e) { StringBuffer sp = new StringBuffer(); - for (AlignmentPanel ap : _alignwith) + for (AlignmentPanel alignPanel : _alignwith) { - sp.append("'" + ap.alignFrame.getTitle() + "' "); + sp.append("'" + alignPanel.alignFrame.getTitle() + "' "); } Cache.log.info("Couldn't align structures with the " + sp.toString() + "associated alignment panels.", e); @@ -905,10 +865,11 @@ public abstract class StructureViewerBase extends GStructureViewer } } // Set the colour using the current view for the associated alignframe - for (AlignmentPanel ap : _colourwith) + for (AlignmentPanel alignPanel : _colourwith) { - binding.colourBySequence(ap); + binding.colourBySequence(alignPanel); } + seqColoursApplied = true; } } @@ -988,6 +949,7 @@ public abstract class StructureViewerBase extends GStructureViewer /** * Configures the title and menu items of the viewer panel. */ + @Override public void updateTitleAndMenus() { AAStructureBindingModel binding = getBinding(); @@ -1028,4 +990,54 @@ public abstract class StructureViewerBase extends GStructureViewer seqColour_actionPerformed(null); } } + + @Override + public String toString() + { + return getTitle(); + } + + @Override + public boolean hasMapping() + { + if (worker != null && (addingStructures || _started)) + { + return false; + } + if (getBinding() == null) + { + if (_aps == null || _aps.size() == 0) + { + // viewer has been closed, but we did at some point run. + return true; + } + return false; + } + String[] pdbids = getBinding().getStructureFiles(); + if (pdbids == null) + { + return false; + } + int p=0; + for (String pdbid:pdbids) { + StructureMapping sm[] = getBinding().getSsm().getMapping(pdbid); + if (sm!=null && sm.length>0 && sm[0]!=null) { + p++; + } + } + // only return true if there is a mapping for every structure file we have loaded + if (p == 0 || p != pdbids.length) + { + return false; + } + // and that coloring has been applied + return seqColoursApplied; + } + + @Override + public void raiseViewer() + { + toFront(); + } + } diff --git a/src/jalview/gui/TreeCanvas.java b/src/jalview/gui/TreeCanvas.java index 7dc1a99..fa30e13 100755 --- a/src/jalview/gui/TreeCanvas.java +++ b/src/jalview/gui/TreeCanvas.java @@ -962,6 +962,15 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } } colourGroups(groups); + + /* + * clear partition (don't show vertical line) if + * it is to the right of all nodes + */ + if (groups.isEmpty()) + { + threshold = 0f; + } } PaintRefresher.Refresh(tp, ap.av.getSequenceSetId()); @@ -981,7 +990,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, Vector l = tree.findLeaves(groups.get(i)); - Vector sequences = new Vector(); + Vector sequences = new Vector<>(); for (int j = 0; j < l.size(); j++) { diff --git a/src/jalview/gui/VamsasApplication.java b/src/jalview/gui/VamsasApplication.java index d2086e0..973cfe8 100644 --- a/src/jalview/gui/VamsasApplication.java +++ b/src/jalview/gui/VamsasApplication.java @@ -1074,16 +1074,16 @@ public class VamsasApplication implements SelectionSource, VamsasSource } else { - // int[] intervals = colsel.getVisibleContigs( - // seqsel.getStartRes(), seqsel.getEndRes() + 1); - int[] intervals = hidden.getVisibleContigs( - seqsel.getStartRes(), seqsel.getEndRes() + 1); - for (int iv = 0; iv < intervals.length; iv += 2) + Iterator intervals = hidden + .getVisContigsIterator(seqsel.getStartRes(), + seqsel.getEndRes() + 1, false); + while (intervals.hasNext()) { + int[] region = intervals.next(); Seg s = new Seg(); - s.setStart(intervals[iv] + 1); // vamsas indices begin at - // 1, not zero. - s.setEnd(intervals[iv + 1] + 1); + s.setStart(region[0] + 1); // vamsas indices begin at 1, + // not zero. + s.setEnd(region[1] + 1); s.setInclusive(true); range.addSeg(s); } diff --git a/src/jalview/io/AlignFile.java b/src/jalview/io/AlignFile.java index 2340283..497f0a5 100755 --- a/src/jalview/io/AlignFile.java +++ b/src/jalview/io/AlignFile.java @@ -72,7 +72,20 @@ public abstract class AlignFile extends FileParse long end; - private boolean parseCalled; + /** + * true if parse() has been called + */ + private boolean parseCalled = false; + + private boolean parseImmediately = true; + + /** + * @return if doParse() was called at construction time + */ + protected boolean isParseImmediately() + { + return parseImmediately; + } /** * Creates a new AlignFile object. @@ -153,6 +166,11 @@ public abstract class AlignFile extends FileParse { super(source); initData(); + + // stash flag in case parse needs to know if it has to autoconfigure or was + // configured after construction + this.parseImmediately = parseImmediately; + if (parseImmediately) { doParse(); diff --git a/src/jalview/io/AnnotationFile.java b/src/jalview/io/AnnotationFile.java index 00476d6..e578a45 100755 --- a/src/jalview/io/AnnotationFile.java +++ b/src/jalview/io/AnnotationFile.java @@ -968,7 +968,7 @@ public class AnnotationFile else { // consider deferring this till after the file has been parsed ? - hidden.hideInsertionsFor(sr); + hidden.hideList(sr.getInsertions()); } } modified = true; diff --git a/src/jalview/io/ClustalFile.java b/src/jalview/io/ClustalFile.java index c21b02c..afb2009 100755 --- a/src/jalview/io/ClustalFile.java +++ b/src/jalview/io/ClustalFile.java @@ -26,7 +26,8 @@ import jalview.datamodel.SequenceI; import jalview.util.Format; import java.io.IOException; -import java.util.Hashtable; +import java.util.HashMap; +import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; @@ -59,12 +60,11 @@ public class ClustalFile extends AlignFile { int i = 0; boolean flag = false; - boolean rna = false; boolean top = false; - StringBuffer pssecstr = new StringBuffer(), - consstr = new StringBuffer(); - Vector headers = new Vector(); - Hashtable seqhash = new Hashtable(); + StringBuffer pssecstr = new StringBuffer(); + StringBuffer consstr = new StringBuffer(); + Vector headers = new Vector<>(); + Map seqhash = new HashMap<>(); StringBuffer tempseq; String line, id; StringTokenizer str; @@ -77,9 +77,11 @@ public class ClustalFile extends AlignFile { top = true; } - if (line.indexOf(" ") != 0) + boolean isConservation = line.startsWith(SPACE) + || line.startsWith(TAB); + if (!isConservation) { - str = new StringTokenizer(line, " "); + str = new StringTokenizer(line); if (str.hasMoreTokens()) { @@ -95,7 +97,7 @@ public class ClustalFile extends AlignFile { if (seqhash.containsKey(id)) { - tempseq = (StringBuffer) seqhash.get(id); + tempseq = seqhash.get(id); } else { @@ -173,7 +175,7 @@ public class ClustalFile extends AlignFile AlignmentAnnotation lastssa = null; if (pssecstr.length() == maxLength) { - Vector ss = new Vector(); + Vector ss = new Vector<>(); AlignmentAnnotation ssa = lastssa = StockholmFile .parseAnnotationRow(ss, "secondary structure", pssecstr.toString()); @@ -182,7 +184,7 @@ public class ClustalFile extends AlignFile } if (consstr.length() == maxLength) { - Vector ss = new Vector(); + Vector ss = new Vector<>(); AlignmentAnnotation ssa = StockholmFile.parseAnnotationRow(ss, "secondary structure", consstr.toString()); ssa.label = "Consensus Secondary Structure"; @@ -238,19 +240,19 @@ public class ClustalFile extends AlignFile out.append(new Format("%-" + maxid + "s") .form(printId(s[j], jvsuffix) + " ")); - int start = i * len; - int end = start + len; + int chunkStart = i * len; + int chunkEnd = chunkStart + len; int length = s[j].getLength(); - if ((end < length) && (start < length)) + if ((chunkEnd < length) && (chunkStart < length)) { - out.append(s[j].getSequenceAsString(start, end)); + out.append(s[j].getSequenceAsString(chunkStart, chunkEnd)); } else { - if (start < length) + if (chunkStart < length) { - out.append(s[j].getSequenceAsString().substring(start)); + out.append(s[j].getSequenceAsString().substring(chunkStart)); } } diff --git a/src/jalview/io/FeaturesFile.java b/src/jalview/io/FeaturesFile.java index d2282b1..169da5a 100755 --- a/src/jalview/io/FeaturesFile.java +++ b/src/jalview/io/FeaturesFile.java @@ -31,6 +31,8 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.io.gff.GffHelperBase; import jalview.io.gff.GffHelperFactory; import jalview.io.gff.GffHelperI; @@ -68,12 +70,20 @@ import java.util.Map.Entry; */ public class FeaturesFile extends AlignFile implements FeaturesSourceI { + private static final String TAB_REGEX = "\\t"; + + private static final String STARTGROUP = "STARTGROUP"; + + private static final String ENDGROUP = "ENDGROUP"; + + private static final String STARTFILTERS = "STARTFILTERS"; + + private static final String ENDFILTERS = "ENDFILTERS"; + private static final String ID_NOT_SPECIFIED = "ID_NOT_SPECIFIED"; private static final String NOTE = "Note"; - protected static final String TAB = "\t"; - protected static final String GFF_VERSION = "##gff-version"; private AlignmentI lastmatchedAl = null; @@ -169,7 +179,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI * @param align * - alignment/dataset containing sequences that are to be annotated * @param colours - * - hashtable to store feature colour definitions + * - map to store feature colour definitions * @param removeHTML * - process html strings into plain text * @param relaxedIdmatching @@ -180,11 +190,34 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI Map colours, boolean removeHTML, boolean relaxedIdmatching) { - Map gffProps = new HashMap(); + return parse(align, colours, null, removeHTML, relaxedIdmatching); + } + + /** + * Parse GFF or Jalview format sequence features file + * + * @param align + * - alignment/dataset containing sequences that are to be annotated + * @param colours + * - map to store feature colour definitions + * @param filters + * - map to store feature filter definitions + * @param removeHTML + * - process html strings into plain text + * @param relaxedIdmatching + * - when true, ID matches to compound sequence IDs are allowed + * @return true if features were added + */ + public boolean parse(AlignmentI align, + Map colours, + Map filters, boolean removeHTML, + boolean relaxedIdmatching) + { + Map gffProps = new HashMap<>(); /* * keep track of any sequences we try to create from the data */ - List newseqs = new ArrayList(); + List newseqs = new ArrayList<>(); String line = null; try @@ -204,7 +237,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI continue; } - gffColumns = line.split("\\t"); // tab as regex + gffColumns = line.split(TAB_REGEX); if (gffColumns.length == 1) { if (line.trim().equalsIgnoreCase("GFF")) @@ -218,18 +251,23 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI } } - if (gffColumns.length > 1 && gffColumns.length < 4) + if (gffColumns.length > 0 && gffColumns.length < 4) { /* * if 2 or 3 tokens, we anticipate either 'startgroup', 'endgroup' or * a feature type colour specification */ String ft = gffColumns[0]; - if (ft.equalsIgnoreCase("startgroup")) + if (ft.equalsIgnoreCase(STARTFILTERS)) + { + parseFilters(filters); + continue; + } + if (ft.equalsIgnoreCase(STARTGROUP)) { featureGroup = gffColumns[1]; } - else if (ft.equalsIgnoreCase("endgroup")) + else if (ft.equalsIgnoreCase(ENDGROUP)) { // We should check whether this is the current group, // but at present there's no way of showing more than 1 group @@ -290,6 +328,43 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI } /** + * Reads input lines from STARTFILTERS to ENDFILTERS and adds a feature type + * filter to the map for each line parsed. After exit from this method, + * nextLine() should return the line after ENDFILTERS (or we are already at + * end of file if ENDFILTERS was missing). + * + * @param filters + * @throws IOException + */ + protected void parseFilters(Map filters) + throws IOException + { + String line; + while ((line = nextLine()) != null) + { + if (line.toUpperCase().startsWith(ENDFILTERS)) + { + return; + } + String[] tokens = line.split(TAB_REGEX); + if (tokens.length != 2) + { + System.err.println(String.format("Invalid token count %d for %d", + tokens.length, line)); + } + else + { + String featureType = tokens[0]; + FeatureMatcherSetI fm = FeatureMatcherSet.fromString(tokens[1]); + if (fm != null && filters != null) + { + filters.put(featureType, fm); + } + } + } + } + + /** * Try to parse a Jalview format feature specification and add it as a * sequence feature to any matching sequences in the alignment. Returns true * if successful (a feature was added), or false if not. @@ -487,15 +562,16 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI } /** - * Returns contents of a Jalview format features file, for visible features, - * as filtered by type and group. Features with a null group are displayed if - * their feature type is visible. Non-positional features may optionally be - * included (with no check on type or group). + * Returns contents of a Jalview format features file, for visible features, as + * filtered by type and group. Features with a null group are displayed if their + * feature type is visible. Non-positional features may optionally be included + * (with no check on type or group). * * @param sequences * source of features * @param visible * map of colour for each visible feature type + * @param featureFilters * @param visibleFeatureGroups * @param includeNonPositional * if true, include non-positional features (regardless of group or @@ -504,6 +580,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI */ public String printJalviewFormat(SequenceI[] sequences, Map visible, + Map featureFilters, List visibleFeatureGroups, boolean includeNonPositional) { if (!includeNonPositional && (visible == null || visible.isEmpty())) @@ -531,10 +608,15 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI .toArray(new String[visible.keySet().size()]); /* + * feature filters if any + */ + outputFeatureFilters(out, visible, featureFilters); + + /* * sort groups alphabetically, and ensure that features with a * null or empty group are output after those in named groups */ - List sortedGroups = new ArrayList(visibleFeatureGroups); + List sortedGroups = new ArrayList<>(visibleFeatureGroups); sortedGroups.remove(null); sortedGroups.remove(""); Collections.sort(sortedGroups); @@ -560,13 +642,76 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI } } - for (String group : sortedGroups) + /* + * positional features within groups + */ + foundSome |= outputFeaturesByGroup(out, sortedGroups, types, sequences); + + return foundSome ? out.toString() : "No Features Visible"; + } + + /** + * Outputs any feature filters defined for visible feature types, sandwiched by + * STARTFILTERS and ENDFILTERS lines + * + * @param out + * @param visible + * @param featureFilters + */ + void outputFeatureFilters(StringBuilder out, + Map visible, + Map featureFilters) + { + if (visible == null || featureFilters == null + || featureFilters.isEmpty()) + { + return; + } + + boolean first = true; + for (String featureType : visible.keySet()) + { + FeatureMatcherSetI filter = featureFilters.get(featureType); + if (filter != null) + { + if (first) + { + first = false; + out.append(newline).append(STARTFILTERS).append(newline); + } + out.append(featureType).append(TAB).append(filter.toStableString()) + .append(newline); + } + } + if (!first) + { + out.append(ENDFILTERS).append(newline).append(newline); + } + + } + + /** + * Appends output of sequence features within feature groups to the output + * buffer. Groups other than the null or empty group are sandwiched by + * STARTGROUP and ENDGROUP lines. + * + * @param out + * @param groups + * @param featureTypes + * @param sequences + * @return + */ + private boolean outputFeaturesByGroup(StringBuilder out, + List groups, String[] featureTypes, SequenceI[] sequences) + { + boolean foundSome = false; + for (String group : groups) { boolean isNamedGroup = (group != null && !"".equals(group)); if (isNamedGroup) { out.append(newline); - out.append("STARTGROUP").append(TAB); + out.append(STARTGROUP).append(TAB); out.append(group); out.append(newline); } @@ -577,11 +722,11 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI for (int i = 0; i < sequences.length; i++) { String sequenceName = sequences[i].getName(); - List features = new ArrayList(); - if (types.length > 0) + List features = new ArrayList<>(); + if (featureTypes.length > 0) { features.addAll(sequences[i].getFeatures().getFeaturesForGroup( - true, group, types)); + true, group, featureTypes)); } for (SequenceFeature sequenceFeature : features) @@ -593,13 +738,12 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI if (isNamedGroup) { - out.append("ENDGROUP").append(TAB); + out.append(ENDGROUP).append(TAB); out.append(group); out.append(newline); } } - - return foundSome ? out.toString() : "No Features Visible"; + return foundSome; } /** @@ -688,7 +832,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI dataset = new Alignment(new SequenceI[] {}); } - Map featureColours = new HashMap(); + Map featureColours = new HashMap<>(); boolean parseResult = parse(dataset, featureColours, false, true); if (!parseResult) { @@ -748,7 +892,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI for (SequenceI seq : sequences) { - List features = new ArrayList(); + List features = new ArrayList<>(); if (includeNonPositionalFeatures) { features.addAll(seq.getFeatures().getNonPositionalFeatures()); diff --git a/src/jalview/io/FileFormat.java b/src/jalview/io/FileFormat.java index 4b33dbf..e94e1ce 100644 --- a/src/jalview/io/FileFormat.java +++ b/src/jalview/io/FileFormat.java @@ -347,7 +347,7 @@ public enum FileFormat implements FileFormatI return true; } }, - Jalview("Jalview", "jar,jvp", true, true) + Jalview("Jalview", "jvp, jar", true, true) { @Override public AlignmentFileReaderI getReader(FileParse source) diff --git a/src/jalview/io/FileParse.java b/src/jalview/io/FileParse.java index c0328d5..7117d0f 100755 --- a/src/jalview/io/FileParse.java +++ b/src/jalview/io/FileParse.java @@ -45,6 +45,10 @@ import java.util.zip.GZIPInputStream; */ public class FileParse { + protected static final String SPACE = " "; + + protected static final String TAB = "\t"; + /** * text specifying source of data. usually filename or url. */ diff --git a/src/jalview/io/FormatAdapter.java b/src/jalview/io/FormatAdapter.java index 7647a16..6d3c18a 100755 --- a/src/jalview/io/FormatAdapter.java +++ b/src/jalview/io/FormatAdapter.java @@ -212,12 +212,12 @@ public class FormatAdapter extends AppletFormatAdapter AlignmentAnnotation na = new AlignmentAnnotation(ala[i]); if (selgp != null) { - hidden.makeVisibleAnnotation(selgp.getStartRes(), - selgp.getEndRes(), na); + na.makeVisibleAnnotation(selgp.getStartRes(), selgp.getEndRes(), + hidden); } else { - hidden.makeVisibleAnnotation(na); + na.makeVisibleAnnotation(hidden); } alv.addAnnotation(na); } diff --git a/src/jalview/io/JalviewFileChooser.java b/src/jalview/io/JalviewFileChooser.java index c49e34f..7a21c16 100755 --- a/src/jalview/io/JalviewFileChooser.java +++ b/src/jalview/io/JalviewFileChooser.java @@ -68,8 +68,8 @@ public class JalviewFileChooser extends JFileChooser public static JalviewFileChooser forRead(String directory, String selected) { - List extensions = new ArrayList(); - List descs = new ArrayList(); + List extensions = new ArrayList<>(); + List descs = new ArrayList<>(); for (FileFormatI format : FileFormats.getInstance().getFormats()) { if (format.isReadable()) @@ -96,8 +96,8 @@ public class JalviewFileChooser extends JFileChooser { // TODO in Java 8, forRead and forWrite can be a single method // with a lambda expression parameter for isReadable/isWritable - List extensions = new ArrayList(); - List descs = new ArrayList(); + List extensions = new ArrayList<>(); + List descs = new ArrayList<>(); for (FileFormatI format : FileFormats.getInstance().getFormats()) { if (format.isWritable()) @@ -142,7 +142,7 @@ public class JalviewFileChooser extends JFileChooser super(safePath(dir)); if (extensions.length == descs.length) { - List formats = new ArrayList(); + List formats = new ArrayList<>(); for (int i = 0; i < extensions.length; i++) { formats.add(new String[] { extensions[i], descs[i] }); @@ -278,6 +278,19 @@ public class JalviewFileChooser extends JFileChooser return null; } + File ourselectedFile = null; + + @Override + public File getSelectedFile() + { + File selfile = super.getSelectedFile(); + if (selfile == null && ourselectedFile != null) + { + return ourselectedFile; + } + return selfile; + } + @Override public int showSaveDialog(Component parent) throws HeadlessException { @@ -285,23 +298,48 @@ public class JalviewFileChooser extends JFileChooser setDialogType(SAVE_DIALOG); + this.setSelectedFile(null); int ret = showDialog(parent, MessageManager.getString("action.save")); + ourselectedFile = getSelectedFile(); + if (getSelectedFile() == null) + { + // Workaround for Java 9,10 on OSX - no selected file, but there is a + // filename typed in + try + { + String filename = ((BasicFileChooserUI) getUI()).getFileName(); + if (filename != null && filename.length() > 0) + { + ourselectedFile = new File(getCurrentDirectory(), filename); + } + } catch (Throwable x) + { + System.err.println( + "Unexpected exception when trying to get filename."); + x.printStackTrace(); + } + } + if (ourselectedFile == null) + { + return JalviewFileChooser.CANCEL_OPTION; + } if (getFileFilter() instanceof JalviewFileFilter) { JalviewFileFilter jvf = (JalviewFileFilter) getFileFilter(); - if (!jvf.accept(getSelectedFile())) + if (!jvf.accept(ourselectedFile)) { - String withExtension = getSelectedFile() + "." + String withExtension = getSelectedFile().getName() + "." + jvf.getAcceptableExtension(); - setSelectedFile(new File(withExtension)); + ourselectedFile = (new File(getCurrentDirectory(), withExtension)); + setSelectedFile(ourselectedFile); } } // TODO: ENSURE THAT FILES SAVED WITH A ':' IN THE NAME ARE REFUSED AND THE // USER PROMPTED FOR A NEW FILENAME if ((ret == JalviewFileChooser.APPROVE_OPTION) - && getSelectedFile().exists()) + && ourselectedFile.exists()) { int confirm = JvOptionPane.showConfirmDialog(parent, MessageManager.getString("label.overwrite_existing_file"), diff --git a/src/jalview/io/JalviewFileFilter.java b/src/jalview/io/JalviewFileFilter.java index d59e88a..21f5b0f 100755 --- a/src/jalview/io/JalviewFileFilter.java +++ b/src/jalview/io/JalviewFileFilter.java @@ -21,8 +21,10 @@ package jalview.io; import java.io.File; -import java.util.Enumeration; import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.StringTokenizer; import javax.swing.filechooser.FileFilter; @@ -31,7 +33,7 @@ public class JalviewFileFilter extends FileFilter { public static Hashtable suffixHash = new Hashtable(); - private Hashtable filters = null; + private Map filters = null; private String description = "no description"; @@ -72,10 +74,11 @@ public class JalviewFileFilter extends FileFilter public String getAcceptableExtension() { - return filters.keys().nextElement().toString(); + return filters.keySet().iterator().next().toString(); } // takes account of the fact that database is a directory + @Override public boolean accept(File f) { if (f != null) @@ -87,7 +90,7 @@ public class JalviewFileFilter extends FileFilter return true; } - if ((extension != null) && (filters.get(getExtension(f)) != null)) + if ((extension != null) && (filters.get(extension) != null)) { return true; } @@ -118,13 +121,14 @@ public class JalviewFileFilter extends FileFilter { if (filters == null) { - filters = new Hashtable(5); + filters = new LinkedHashMap<>(5); } filters.put(extension.toLowerCase(), this); fullDescription = null; } + @Override public String getDescription() { if (fullDescription == null) @@ -135,15 +139,15 @@ public class JalviewFileFilter extends FileFilter : (description + " ("); // build the description from the extension list - Enumeration extensions = filters.keys(); + Iterator extensions = filters.keySet().iterator(); if (extensions != null) { - fullDescription += ("." + (String) extensions.nextElement()); + fullDescription += ("." + extensions.next()); - while (extensions.hasMoreElements()) + while (extensions.hasNext()) { - fullDescription += (", " + (String) extensions.nextElement()); + fullDescription += (", " + extensions.next()); } } diff --git a/src/jalview/io/JalviewFileView.java b/src/jalview/io/JalviewFileView.java index b2fe587..18114f3 100755 --- a/src/jalview/io/JalviewFileView.java +++ b/src/jalview/io/JalviewFileView.java @@ -37,14 +37,15 @@ public class JalviewFileView extends FileView private void loadExtensions() { - extensions = new HashMap(); + extensions = new HashMap<>(); for (FileFormatI ff : FileFormats.getInstance().getFormats()) { String desc = ff.getName() + " file"; String exts = ff.getExtensions(); for (String ext : exts.split(",")) { - extensions.put(ext.trim().toLowerCase(), + ext = ext.trim().toLowerCase(); + extensions.put(ext, desc + ("jar".equals(ext) ? " (old)" : "")); } } @@ -124,7 +125,7 @@ public class JalviewFileView extends FileView { if (icons == null) { - icons = new HashMap(); + icons = new HashMap<>(); } if (!icons.containsKey(filePath)) { diff --git a/src/jalview/io/SequenceAnnotationReport.java b/src/jalview/io/SequenceAnnotationReport.java index f1ebcac..6b82671 100644 --- a/src/jalview/io/SequenceAnnotationReport.java +++ b/src/jalview/io/SequenceAnnotationReport.java @@ -20,13 +20,15 @@ */ package jalview.io; +import jalview.api.FeatureColourI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.DBRefSource; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; -import jalview.io.gff.GffConstants; import jalview.util.MessageManager; +import jalview.util.StringUtils; import jalview.util.UrlLink; +import jalview.viewmodel.seqfeatures.FeatureRendererModel; import java.util.Arrays; import java.util.Collection; @@ -58,7 +60,7 @@ public class SequenceAnnotationReport /* * Comparator to order DBRefEntry by Source + accession id (case-insensitive), - * with 'Primary' sources placed before others + * with 'Primary' sources placed before others, and 'chromosome' first of all */ private static Comparator comparator = new Comparator() { @@ -66,6 +68,14 @@ public class SequenceAnnotationReport @Override public int compare(DBRefEntry ref1, DBRefEntry ref2) { + if (ref1.isChromosome()) + { + return -1; + } + if (ref2.isChromosome()) + { + return 1; + } String s1 = ref1.getSource(); String s2 = ref2.getSource(); boolean s1Primary = isPrimarySource(s1); @@ -78,14 +88,14 @@ public class SequenceAnnotationReport { return 1; } - int comp = s1 == null ? -1 - : (s2 == null ? 1 : s1.compareToIgnoreCase(s2)); + int comp = s1 == null ? -1 : (s2 == null ? 1 : s1 + .compareToIgnoreCase(s2)); if (comp == 0) { String a1 = ref1.getAccessionId(); String a2 = ref2.getAccessionId(); - comp = a1 == null ? -1 - : (a2 == null ? 1 : a1.compareToIgnoreCase(a2)); + comp = a1 == null ? -1 : (a2 == null ? 1 : a1 + .compareToIgnoreCase(a2)); } return comp; } @@ -106,9 +116,9 @@ public class SequenceAnnotationReport } }; - public SequenceAnnotationReport(String linkImageURL) + public SequenceAnnotationReport(String linkURL) { - this.linkImageURL = linkImageURL; + this.linkImageURL = linkURL; } /** @@ -120,13 +130,13 @@ public class SequenceAnnotationReport * @param minmax */ public void appendFeatures(final StringBuilder sb, int rpos, - List features, Map minmax) + List features, FeatureRendererModel fr) { if (features != null) { for (SequenceFeature feature : features) { - appendFeature(sb, rpos, minmax, feature); + appendFeature(sb, rpos, fr, feature); } } } @@ -140,7 +150,7 @@ public class SequenceAnnotationReport * @param feature */ void appendFeature(final StringBuilder sb, int rpos, - Map minmax, SequenceFeature feature) + FeatureRendererModel fr, SequenceFeature feature) { if (feature.isContactFeature()) { @@ -153,99 +163,92 @@ public class SequenceAnnotationReport sb.append(feature.getType()).append(" ").append(feature.getBegin()) .append(":").append(feature.getEnd()); } + return; } - else + + if (sb.length() > 6) { - if (sb.length() > 6) + sb.append("
            "); + } + // TODO: remove this hack to display link only features + boolean linkOnly = feature.getValue("linkonly") != null; + if (!linkOnly) + { + sb.append(feature.getType()).append(" "); + if (rpos != 0) { - sb.append("
            "); + // we are marking a positional feature + sb.append(feature.begin); } - // TODO: remove this hack to display link only features - boolean linkOnly = feature.getValue("linkonly") != null; - if (!linkOnly) + if (feature.begin != feature.end) { - sb.append(feature.getType()).append(" "); - if (rpos != 0) - { - // we are marking a positional feature - sb.append(feature.begin); - } - if (feature.begin != feature.end) - { - sb.append(" ").append(feature.end); - } + sb.append(" ").append(feature.end); + } - if (feature.getDescription() != null - && !feature.description.equals(feature.getType())) - { - String tmpString = feature.getDescription(); - String tmp2up = tmpString.toUpperCase(); - int startTag = tmp2up.indexOf(""); - if (startTag > -1) - { - tmpString = tmpString.substring(startTag + 6); - tmp2up = tmp2up.substring(startTag + 6); - } - int endTag = tmp2up.indexOf(""); - if (endTag > -1) - { - tmpString = tmpString.substring(0, endTag); - tmp2up = tmp2up.substring(0, endTag); - } - endTag = tmp2up.indexOf(""); - if (endTag > -1) - { - tmpString = tmpString.substring(0, endTag); - } + String description = feature.getDescription(); + if (description != null && !description.equals(feature.getType())) + { + description = StringUtils.stripHtmlTags(description); + sb.append("; ").append(description); + } - if (startTag > -1) - { - sb.append("; ").append(tmpString); - } - else - { - if (tmpString.indexOf("<") > -1 || tmpString.indexOf(">") > -1) - { - // The description does not specify html is to - // be used, so we must remove < > symbols - tmpString = tmpString.replaceAll("<", "<"); - tmpString = tmpString.replaceAll(">", ">"); + if (showScore(feature, fr)) + { + sb.append(" Score=").append(String.valueOf(feature.getScore())); + } + String status = (String) feature.getValue("status"); + if (status != null && status.length() > 0) + { + sb.append("; (").append(status).append(")"); + } - sb.append("; "); - sb.append(tmpString); - } - else - { - sb.append("; ").append(tmpString); - } - } - } - // check score should be shown - if (!Float.isNaN(feature.getScore())) + /* + * add attribute value if coloured by attribute + */ + if (fr != null) + { + FeatureColourI fc = fr.getFeatureColours().get(feature.getType()); + if (fc != null && fc.isColourByAttribute()) { - float[][] rng = (minmax == null) ? null - : minmax.get(feature.getType()); - if (rng != null && rng[0] != null && rng[0][0] != rng[0][1]) + String[] attName = fc.getAttributeName(); + String attVal = feature.getValueAsString(attName); + if (attVal != null) { - sb.append(" Score=").append(String.valueOf(feature.getScore())); + sb.append("; ").append(String.join(":", attName)).append("=") + .append(attVal); } } - String status = (String) feature.getValue("status"); - if (status != null && status.length() > 0) - { - sb.append("; (").append(status).append(")"); - } - String clinSig = (String) feature - .getValue(GffConstants.CLINICAL_SIGNIFICANCE); - if (clinSig != null) - { - sb.append("; ").append(clinSig); - } } } } /** + * Answers true if score should be shown, else false. Score is shown if it is + * not NaN, and the feature type has a non-trivial min-max score range + */ + boolean showScore(SequenceFeature feature, FeatureRendererModel fr) + { + if (Float.isNaN(feature.getScore())) + { + return false; + } + if (fr == null) + { + return true; + } + float[][] minMax = fr.getMinMax().get(feature.getType()); + + /* + * minMax[0] is the [min, max] score range for positional features + */ + if (minMax == null || minMax[0] == null || minMax[0][0] == minMax[0][1]) + { + return false; + } + return true; + } + + /** * Format and appends any hyperlinks for the sequence feature to the string * buffer * @@ -268,19 +271,20 @@ public class SequenceAnnotationReport { for (List urllink : createLinksFrom(null, urlstring)) { - sb.append("
            " + sb.append("
            " + (urllink.get(0).toLowerCase() - .equals(urllink.get(1).toLowerCase()) - ? urllink.get(0) - : (urllink.get(0) + ":" - + urllink.get(1))) - + "
            "); + .equals(urllink.get(1).toLowerCase()) ? urllink + .get(0) : (urllink.get(0) + ":" + urllink + .get(1))) + "
            "); } } catch (Exception x) { - System.err.println( - "problem when creating links from " + urlstring); + System.err.println("problem when creating links from " + + urlstring); x.printStackTrace(); } } @@ -298,7 +302,7 @@ public class SequenceAnnotationReport */ Collection> createLinksFrom(SequenceI seq, String link) { - Map> urlSets = new LinkedHashMap>(); + Map> urlSets = new LinkedHashMap<>(); UrlLink urlLink = new UrlLink(link); if (!urlLink.isValid()) { @@ -313,10 +317,10 @@ public class SequenceAnnotationReport public void createSequenceAnnotationReport(final StringBuilder tip, SequenceI sequence, boolean showDbRefs, boolean showNpFeats, - Map minmax) + FeatureRendererModel fr) { createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats, - minmax, false); + fr, false); } /** @@ -331,13 +335,13 @@ public class SequenceAnnotationReport * whether to include database references for the sequence * @param showNpFeats * whether to include non-positional sequence features - * @param minmax + * @param fr * @param summary * @return */ int createSequenceAnnotationReport(final StringBuilder sb, SequenceI sequence, boolean showDbRefs, boolean showNpFeats, - Map minmax, boolean summary) + FeatureRendererModel fr, boolean summary) { String tmp; sb.append(""); @@ -354,7 +358,7 @@ public class SequenceAnnotationReport { ds = ds.getDatasetSequence(); } - + if (showDbRefs) { maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary)); @@ -369,7 +373,7 @@ public class SequenceAnnotationReport .getNonPositionalFeatures()) { int sz = -sb.length(); - appendFeature(sb, 0, minmax, sf); + appendFeature(sb, 0, fr, sf); sz += sb.length(); maxWidth = Math.max(maxWidth, sz); } @@ -458,8 +462,7 @@ public class SequenceAnnotationReport } if (moreSources) { - sb.append("
            ").append(source) - .append(COMMA).append(ELLIPSIS); + sb.append("
            ").append(source).append(COMMA).append(ELLIPSIS); } if (ellipsis) { @@ -473,10 +476,10 @@ public class SequenceAnnotationReport public void createTooltipAnnotationReport(final StringBuilder tip, SequenceI sequence, boolean showDbRefs, boolean showNpFeats, - Map minmax) + FeatureRendererModel fr) { - int maxWidth = createSequenceAnnotationReport(tip, sequence, showDbRefs, - showNpFeats, minmax, true); + int maxWidth = createSequenceAnnotationReport(tip, sequence, + showDbRefs, showNpFeats, fr, true); if (maxWidth > 60) { diff --git a/src/jalview/io/StockholmFile.java b/src/jalview/io/StockholmFile.java index f5b5177..0e73af1 100644 --- a/src/jalview/io/StockholmFile.java +++ b/src/jalview/io/StockholmFile.java @@ -83,6 +83,14 @@ public class StockholmFile extends AlignFile public static final Regex DETECT_BRACKETS = new Regex( "(<|>|\\[|\\]|\\(|\\)|\\{|\\})"); + // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using NOT_RNASS first. + public static final String RNASS_BRACKETS = "<>[](){}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"; + + // use the following regex to decide an annotations (whole) line is NOT an RNA + // SS (it contains only E,H,e,h and other non-brace/non-alpha chars) + private static final Regex NOT_RNASS = new Regex( + "^[^<>[\\](){}A-DF-Za-df-z]*$"); + StringBuffer out; // output buffer AlignmentI al; @@ -197,7 +205,7 @@ public class StockholmFile extends AlignFile String version; // String id; Hashtable seqAnn = new Hashtable(); // Sequence related annotations - LinkedHashMap seqs = new LinkedHashMap(); + LinkedHashMap seqs = new LinkedHashMap<>(); Regex p, r, rend, s, x; // Temporary line for processing RNA annotation // String RNAannot = ""; @@ -658,7 +666,7 @@ public class StockholmFile extends AlignFile strucAnn = new Hashtable(); } - Vector newStruc = new Vector(); + Vector newStruc = new Vector<>(); parseAnnotationRow(newStruc, type, ns); for (AlignmentAnnotation alan : newStruc) { @@ -710,7 +718,7 @@ public class StockholmFile extends AlignFile private void guessDatabaseFor(Sequence seqO, String dbr, String dbsource) { DBRefEntry dbrf = null; - List dbrs = new ArrayList(); + List dbrs = new ArrayList<>(); String seqdb = "Unknown", sdbac = "" + dbr; int st = -1, en = -1, p; if ((st = sdbac.indexOf("/")) > -1) @@ -824,9 +832,14 @@ public class StockholmFile extends AlignFile } boolean ss = false, posterior = false; type = id2type(type); + + boolean isrnass = false; if (type.equalsIgnoreCase("secondary structure")) { ss = true; + isrnass = !NOT_RNASS.search(annots); // sorry about the double negative + // here (it's easier for dealing with + // other non-alpha-non-brace chars) } if (type.equalsIgnoreCase("posterior probability")) { @@ -844,7 +857,7 @@ public class StockholmFile extends AlignFile { // if (" .-_".indexOf(pos) == -1) { - if (DETECT_BRACKETS.search(pos)) + if (isrnass && RNASS_BRACKETS.indexOf(pos) >= 0) { ann.secondaryStructure = Rna.getRNASecStrucState(pos).charAt(0); ann.displayCharacter = "" + pos.charAt(0); @@ -1114,22 +1127,36 @@ public class StockholmFile extends AlignFile String ch = (annot == null) ? ((sequenceI == null) ? "-" : Character.toString(sequenceI.getCharAt(k))) - : annot.displayCharacter; + : (annot.displayCharacter == null + ? String.valueOf(annot.secondaryStructure) + : annot.displayCharacter); + if (ch == null) + { + ch = " "; + } if (key != null && key.equals("SS")) { + char ssannotchar = ' '; + boolean charset = false; if (annot == null) { // sensible gap character - return ' '; + ssannotchar = ' '; + charset = true; } else { // valid secondary structure AND no alternative label (e.g. ' B') if (annot.secondaryStructure > ' ' && ch.length() < 2) { - return annot.secondaryStructure; + ssannotchar = annot.secondaryStructure; + charset = true; } } + if (charset) + { + return (ssannotchar == ' ' && isrna) ? '.' : ssannotchar; + } } if (ch.length() == 0) @@ -1144,7 +1171,9 @@ public class StockholmFile extends AlignFile { seq = ch.charAt(1); } - return seq; + + return (seq == ' ' && key != null && key.equals("SS") && isrna) ? '.' + : seq; } public String print() diff --git a/src/jalview/io/cache/JvCacheableInputBox.java b/src/jalview/io/cache/JvCacheableInputBox.java index a837512..beef3e7 100644 --- a/src/jalview/io/cache/JvCacheableInputBox.java +++ b/src/jalview/io/cache/JvCacheableInputBox.java @@ -23,8 +23,6 @@ package jalview.io.cache; import jalview.bin.Cache; import jalview.util.MessageManager; -import java.awt.Color; -import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; @@ -36,37 +34,22 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import javax.swing.BorderFactory; import javax.swing.JComboBox; -import javax.swing.JLabel; import javax.swing.JMenuItem; -import javax.swing.JPanel; import javax.swing.JPopupMenu; -import javax.swing.JTextField; import javax.swing.SwingUtilities; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.PlainDocument; public class JvCacheableInputBox extends JComboBox { private static final long serialVersionUID = 5774610435079326695L; - private static final int INPUT_LIMIT = 2; - private static final int LEFT_BOARDER_WIDTH = 16; private String cacheKey; private AppCache appCache; - private JPanel pnlDefaultCache = new JPanel(); - - private JLabel lblDefaultCacheSize = new JLabel(); - - private JTextField txtDefaultCacheSize = new JTextField(); - private JPopupMenu popup = new JPopupMenu(); private JMenuItem menuItemClearCache = new JMenuItem(); @@ -123,8 +106,8 @@ public class JvCacheableInputBox extends JComboBox } /** - * Method for initialising cache items for a given cache key and populating - * the in-memory cache with persisted cache items + * Method for initialising cache items for a given cache key and populating the + * in-memory cache with persisted cache items * * @param cacheKey */ @@ -144,7 +127,7 @@ public class JvCacheableInputBox extends JComboBox .getAllCachedItemsFor(cacheKey); if (foundCacheItems == null) { - foundCacheItems = new LinkedHashSet(); + foundCacheItems = new LinkedHashSet<>(); } // populate memory cache for (String cacheItem : persistedCacheItems) @@ -159,48 +142,7 @@ public class JvCacheableInputBox extends JComboBox */ private void initCachePopupMenu() { - pnlDefaultCache.setBackground(Color.WHITE); - // pad panel so as to align with other menu items - pnlDefaultCache.setBorder( - BorderFactory.createEmptyBorder(0, LEFT_BOARDER_WIDTH, 0, 0)); - txtDefaultCacheSize.setPreferredSize(new Dimension(45, 20)); - txtDefaultCacheSize.setFont(new java.awt.Font("Verdana", 0, 12)); - lblDefaultCacheSize - .setText(MessageManager.getString("label.default_cache_size")); - lblDefaultCacheSize.setFont(new java.awt.Font("Verdana", 0, 12)); - // Force input to accept only Integer entries up to length - INPUT_LIMIT - txtDefaultCacheSize.setDocument(new PlainDocument() - { - private static final long serialVersionUID = 1L; - - @Override - public void insertString(int offs, String str, AttributeSet a) - throws BadLocationException - { - if (getLength() + str.length() <= INPUT_LIMIT && isInteger(str)) - { - super.insertString(offs, str, a); - } - } - }); - txtDefaultCacheSize.addKeyListener(new java.awt.event.KeyAdapter() - { - @Override - public void keyPressed(KeyEvent e) - { - if (e.getKeyCode() == KeyEvent.VK_ENTER) - { - e.consume(); - updateCache(); - closePopup(); - } - } - }); - - txtDefaultCacheSize.setText(appCache.getCacheLimit(cacheKey)); - pnlDefaultCache.add(lblDefaultCacheSize); menuItemClearCache.setFont(new java.awt.Font("Verdana", 0, 12)); - pnlDefaultCache.add(txtDefaultCacheSize); menuItemClearCache .setText(MessageManager.getString("action.clear_cached_items")); menuItemClearCache.addActionListener(new ActionListener() @@ -215,18 +157,11 @@ public class JvCacheableInputBox extends JComboBox } }); - popup.insert(pnlDefaultCache, 0); popup.add(menuItemClearCache); setComponentPopupMenu(popup); add(popup); } - private void closePopup() - { - popup.setVisible(false); - popup.transferFocus(); - } - /** * Answers true if input text is an integer * @@ -255,10 +190,7 @@ public class JvCacheableInputBox extends JComboBox @Override public void run() { - int userLimit = txtDefaultCacheSize.getText().trim().isEmpty() - ? Integer.valueOf(AppCache.DEFAULT_LIMIT) - : Integer.valueOf(txtDefaultCacheSize.getText()); - int cacheLimit = appCache.updateCacheLimit(cacheKey, userLimit); + int cacheLimit = Integer.parseInt(appCache.getCacheLimit(cacheKey)); String userInput = getUserInput(); if (userInput != null && !userInput.isEmpty()) { @@ -277,7 +209,7 @@ public class JvCacheableInputBox extends JComboBox removeAllItems(); } Set cacheItems = appCache.getAllCachedItemsFor(cacheKey); - List reversedCacheItems = new ArrayList(); + List reversedCacheItems = new ArrayList<>(); reversedCacheItems.addAll(cacheItems); cacheItems = null; Collections.reverse(reversedCacheItems); @@ -326,10 +258,6 @@ public class JvCacheableInputBox extends JComboBox public void persistCache() { appCache.persistCache(cacheKey); - int userLimit = txtDefaultCacheSize.getText().trim().isEmpty() - ? Integer.valueOf(AppCache.DEFAULT_LIMIT) - : Integer.valueOf(txtDefaultCacheSize.getText()); - appCache.updateCacheLimit(cacheKey, userLimit); } /** diff --git a/src/jalview/io/gff/Gff3Helper.java b/src/jalview/io/gff/Gff3Helper.java index c7e1d7a..a25a014 100644 --- a/src/jalview/io/gff/Gff3Helper.java +++ b/src/jalview/io/gff/Gff3Helper.java @@ -39,6 +39,8 @@ import java.util.Map; */ public class Gff3Helper extends GffHelperBase { + public static final String ALLELES = "alleles"; + protected static final String TARGET = "Target"; protected static final String ID = "ID"; @@ -399,7 +401,7 @@ public class Gff3Helper extends GffHelperBase /* * Ensembl returns dna variants as 'alleles' */ - desc = StringUtils.listToDelimitedString(attributes.get("alleles"), + desc = StringUtils.listToDelimitedString(attributes.get(ALLELES), ","); } diff --git a/src/jalview/io/gff/SequenceOntologyI.java b/src/jalview/io/gff/SequenceOntologyI.java index c0570e0..307e1d1 100644 --- a/src/jalview/io/gff/SequenceOntologyI.java +++ b/src/jalview/io/gff/SequenceOntologyI.java @@ -42,6 +42,15 @@ public interface SequenceOntologyI // SO:0001060 public static final String SEQUENCE_VARIANT = "sequence_variant"; + // SO:0001819 + public static final String SYNONYMOUS_VARIANT = "synonymous_variant"; + + // SO:0001992 + public static final String NONSYNONYMOUS_VARIANT = "nonsynonymous_variant"; + + // SO:0001587 + public static final String STOP_GAINED = "stop_gained"; + // SO:0000147 public static final String EXON = "exon"; diff --git a/src/jalview/io/gff/SequenceOntologyLite.java b/src/jalview/io/gff/SequenceOntologyLite.java index f989f7b..72e906c 100644 --- a/src/jalview/io/gff/SequenceOntologyLite.java +++ b/src/jalview/io/gff/SequenceOntologyLite.java @@ -44,7 +44,7 @@ public class SequenceOntologyLite implements SequenceOntologyI * initial selection of types of interest when processing Ensembl features * NB unlike the full SequenceOntology we don't traverse indirect * child-parent relationships here so e.g. need to list every sub-type - * of gene (direct or indirect) that is of interest + * (direct or indirect) that is of interest */ // @formatter:off private final String[][] TERMS = new String[][] { @@ -75,16 +75,26 @@ public class SequenceOntologyLite implements SequenceOntologyI // there are many more sub-types of ncRNA... /* - * sequence_variant sub-types: + * sequence_variant sub-types */ { "sequence_variant", "sequence_variant" }, + { "structural_variant", "sequence_variant" }, { "feature_variant", "sequence_variant" }, { "gene_variant", "sequence_variant" }, + { "transcript_variant", "sequence_variant" }, // NB Ensembl uses NMD_transcript_variant as if a 'transcript' // but we model it here correctly as per the SO { "NMD_transcript_variant", "sequence_variant" }, - { "transcript_variant", "sequence_variant" }, - { "structural_variant", "sequence_variant" }, + { "missense_variant", "sequence_variant" }, + { "synonymous_variant", "sequence_variant" }, + { "frameshift_variant", "sequence_variant" }, + { "5_prime_UTR_variant", "sequence_variant" }, + { "3_prime_UTR_variant", "sequence_variant" }, + { "stop_gained", "sequence_variant" }, + { "stop_lost", "sequence_variant" }, + { "inframe_deletion", "sequence_variant" }, + { "inframe_insertion", "sequence_variant" }, + { "splice_region_variant", "sequence_variant" }, /* * no sub-types of exon or CDS yet seen in Ensembl @@ -121,8 +131,8 @@ public class SequenceOntologyLite implements SequenceOntologyI public SequenceOntologyLite() { - termsFound = new ArrayList(); - termsNotFound = new ArrayList(); + termsFound = new ArrayList<>(); + termsNotFound = new ArrayList<>(); loadStaticData(); } @@ -131,13 +141,13 @@ public class SequenceOntologyLite implements SequenceOntologyI */ private void loadStaticData() { - parents = new HashMap>(); + parents = new HashMap<>(); for (String[] pair : TERMS) { List p = parents.get(pair[0]); if (p == null) { - p = new ArrayList(); + p = new ArrayList<>(); parents.put(pair[0], p); } p.add(pair[1]); diff --git a/src/jalview/io/vcf/VCFLoader.java b/src/jalview/io/vcf/VCFLoader.java new file mode 100644 index 0000000..de2f18a --- /dev/null +++ b/src/jalview/io/vcf/VCFLoader.java @@ -0,0 +1,1474 @@ +package jalview.io.vcf; + +import jalview.analysis.AlignmentUtils; +import jalview.analysis.Dna; +import jalview.api.AlignViewControllerGuiI; +import jalview.bin.Cache; +import jalview.datamodel.DBRefEntry; +import jalview.datamodel.GeneLociI; +import jalview.datamodel.Mapping; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureAttributeType; +import jalview.datamodel.features.FeatureSource; +import jalview.datamodel.features.FeatureSources; +import jalview.ext.ensembl.EnsemblMap; +import jalview.ext.htsjdk.HtsContigDb; +import jalview.ext.htsjdk.VCFReader; +import jalview.io.gff.Gff3Helper; +import jalview.io.gff.SequenceOntologyI; +import jalview.util.MapList; +import jalview.util.MappingUtils; +import jalview.util.MessageManager; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import htsjdk.samtools.SAMException; +import htsjdk.samtools.SAMSequenceDictionary; +import htsjdk.samtools.SAMSequenceRecord; +import htsjdk.samtools.util.CloseableIterator; +import htsjdk.variant.variantcontext.Allele; +import htsjdk.variant.variantcontext.VariantContext; +import htsjdk.variant.vcf.VCFHeader; +import htsjdk.variant.vcf.VCFHeaderLine; +import htsjdk.variant.vcf.VCFHeaderLineCount; +import htsjdk.variant.vcf.VCFHeaderLineType; +import htsjdk.variant.vcf.VCFInfoHeaderLine; + +/** + * A class to read VCF data (using the htsjdk) and add variants as sequence + * features on dna and any related protein product sequences + * + * @author gmcarstairs + */ +public class VCFLoader +{ + /** + * A class to model the mapping from sequence to VCF coordinates. Cases include + *

              + *
            • a direct 1:1 mapping where the sequence is one of the VCF contigs
            • + *
            • a mapping of sequence to chromosomal coordinates, where sequence and VCF + * use the same reference assembly
            • + *
            • a modified mapping of sequence to chromosomal coordinates, where sequence + * and VCF use different reference assembles
            • + *
            + */ + class VCFMap + { + final String chromosome; + + final MapList map; + + VCFMap(String chr, MapList m) + { + chromosome = chr; + map = m; + } + + @Override + public String toString() + { + return chromosome + ":" + map.toString(); + } + } + + /* + * Lookup keys, and default values, for Preference entries that describe + * patterns for VCF and VEP fields to capture + */ + private static final String VEP_FIELDS_PREF = "VEP_FIELDS"; + + private static final String VCF_FIELDS_PREF = "VCF_FIELDS"; + + private static final String DEFAULT_VCF_FIELDS = ".*"; + + private static final String DEFAULT_VEP_FIELDS = ".*";// "Allele,Consequence,IMPACT,SWISSPROT,SIFT,PolyPhen,CLIN_SIG"; + + /* + * keys to fields of VEP CSQ consequence data + * see https://www.ensembl.org/info/docs/tools/vep/vep_formats.html + */ + private static final String CSQ_CONSEQUENCE_KEY = "Consequence"; + private static final String CSQ_ALLELE_KEY = "Allele"; + private static final String CSQ_ALLELE_NUM_KEY = "ALLELE_NUM"; // 0 (ref), 1... + private static final String CSQ_FEATURE_KEY = "Feature"; // Ensembl stable id + + /* + * default VCF INFO key for VEP consequence data + * NB this can be overridden running VEP with --vcf_info_field + * - we don't handle this case (require identifier to be CSQ) + */ + private static final String CSQ_FIELD = "CSQ"; + + /* + * separator for fields in consequence data is '|' + */ + private static final String PIPE_REGEX = "\\|"; + + /* + * key for Allele Frequency output by VEP + * see http://www.ensembl.org/info/docs/tools/vep/vep_formats.html + */ + private static final String ALLELE_FREQUENCY_KEY = "AF"; + + /* + * delimiter that separates multiple consequence data blocks + */ + private static final String COMMA = ","; + + /* + * the feature group assigned to a VCF variant in Jalview + */ + private static final String FEATURE_GROUP_VCF = "VCF"; + + /* + * internal delimiter used to build keys for assemblyMappings + * + */ + private static final String EXCL = "!"; + + /* + * the VCF file we are processing + */ + protected String vcfFilePath; + + /* + * mappings between VCF and sequence reference assembly regions, as + * key = "species!chromosome!fromAssembly!toAssembly + * value = Map{fromRange, toRange} + */ + private Map> assemblyMappings; + + private VCFReader reader; + + /* + * holds details of the VCF header lines (metadata) + */ + private VCFHeader header; + + /* + * a Dictionary of contigs (if present) referenced in the VCF file + */ + private SAMSequenceDictionary dictionary; + + /* + * the position (0...) of field in each block of + * CSQ (consequence) data (if declared in the VCF INFO header for CSQ) + * see http://www.ensembl.org/info/docs/tools/vep/vep_formats.html + */ + private int csqConsequenceFieldIndex = -1; + private int csqAlleleFieldIndex = -1; + private int csqAlleleNumberFieldIndex = -1; + private int csqFeatureFieldIndex = -1; + + // todo the same fields for SnpEff ANN data if wanted + // see http://snpeff.sourceforge.net/SnpEff_manual.html#input + + /* + * a unique identifier under which to save metadata about feature + * attributes (selected INFO field data) + */ + private String sourceId; + + /* + * The INFO IDs of data that is both present in the VCF file, and + * also matched by any filters for data of interest + */ + List vcfFieldsOfInterest; + + /* + * The field offsets and identifiers for VEP (CSQ) data that is both present + * in the VCF file, and also matched by any filters for data of interest + * for example 0 -> Allele, 1 -> Consequence, ..., 36 -> SIFT, ... + */ + Map vepFieldsOfInterest; + + /** + * Constructor given a VCF file + * + * @param alignment + */ + public VCFLoader(String vcfFile) + { + try + { + initialise(vcfFile); + } catch (IOException e) + { + System.err.println("Error opening VCF file: " + e.getMessage()); + } + + // map of species!chromosome!fromAssembly!toAssembly to {fromRange, toRange} + assemblyMappings = new HashMap<>(); + } + + /** + * Starts a new thread to query and load VCF variant data on to the given + * sequences + *

            + * This method is not thread safe - concurrent threads should use separate + * instances of this class. + * + * @param seqs + * @param gui + */ + public void loadVCF(SequenceI[] seqs, final AlignViewControllerGuiI gui) + { + if (gui != null) + { + gui.setStatus(MessageManager.getString("label.searching_vcf")); + } + + new Thread() + { + @Override + public void run() + { + VCFLoader.this.doLoad(seqs, gui); + } + }.start(); + } + + /** + * Reads the specified contig sequence and adds its VCF variants to it + * + * @param contig + * the id of a single sequence (contig) to load + * @return + */ + public SequenceI loadVCFContig(String contig) + { + String ref = header.getOtherHeaderLine(VCFHeader.REFERENCE_KEY) + .getValue(); + if (ref.startsWith("file://")) + { + ref = ref.substring(7); + } + + SequenceI seq = null; + File dbFile = new File(ref); + + if (dbFile.exists()) + { + HtsContigDb db = new HtsContigDb("", dbFile); + seq = db.getSequenceProxy(contig); + loadSequenceVCF(seq, ref); + db.close(); + } + else + { + System.err.println("VCF reference not found: " + ref); + } + + return seq; + } + + /** + * Loads VCF on to one or more sequences + * + * @param seqs + * @param gui + * optional callback handler for messages + */ + protected void doLoad(SequenceI[] seqs, AlignViewControllerGuiI gui) + { + try + { + VCFHeaderLine ref = header + .getOtherHeaderLine(VCFHeader.REFERENCE_KEY); + String vcfAssembly = ref.getValue(); + + int varCount = 0; + int seqCount = 0; + + /* + * query for VCF overlapping each sequence in turn + */ + for (SequenceI seq : seqs) + { + int added = loadSequenceVCF(seq, vcfAssembly); + if (added > 0) + { + seqCount++; + varCount += added; + transferAddedFeatures(seq); + } + } + if (gui != null) + { + String msg = MessageManager.formatMessage("label.added_vcf", + varCount, seqCount); + gui.setStatus(msg); + if (gui.getFeatureSettingsUI() != null) + { + gui.getFeatureSettingsUI().discoverAllFeatureData(); + } + } + } catch (Throwable e) + { + System.err.println("Error processing VCF: " + e.getMessage()); + e.printStackTrace(); + if (gui != null) + { + gui.setStatus("Error occurred - see console for details"); + } + } finally + { + if (reader != null) + { + try + { + reader.close(); + } catch (IOException e) + { + // ignore + } + } + header = null; + dictionary = null; + } + } + + /** + * Opens the VCF file and parses header data + * + * @param filePath + * @throws IOException + */ + private void initialise(String filePath) throws IOException + { + vcfFilePath = filePath; + + reader = new VCFReader(filePath); + + header = reader.getFileHeader(); + + try + { + dictionary = header.getSequenceDictionary(); + } catch (SAMException e) + { + // ignore - thrown if any contig line lacks length info + } + + sourceId = filePath; + + saveMetadata(sourceId); + + /* + * get offset of CSQ ALLELE_NUM and Feature if declared + */ + parseCsqHeader(); + } + + /** + * Reads metadata (such as INFO field descriptions and datatypes) and saves + * them for future reference + * + * @param theSourceId + */ + void saveMetadata(String theSourceId) + { + List vcfFieldPatterns = getFieldMatchers(VCF_FIELDS_PREF, + DEFAULT_VCF_FIELDS); + vcfFieldsOfInterest = new ArrayList<>(); + + FeatureSource metadata = new FeatureSource(theSourceId); + + for (VCFInfoHeaderLine info : header.getInfoHeaderLines()) + { + String attributeId = info.getID(); + String desc = info.getDescription(); + VCFHeaderLineType type = info.getType(); + FeatureAttributeType attType = null; + switch (type) + { + case Character: + attType = FeatureAttributeType.Character; + break; + case Flag: + attType = FeatureAttributeType.Flag; + break; + case Float: + attType = FeatureAttributeType.Float; + break; + case Integer: + attType = FeatureAttributeType.Integer; + break; + case String: + attType = FeatureAttributeType.String; + break; + } + metadata.setAttributeName(attributeId, desc); + metadata.setAttributeType(attributeId, attType); + + if (isFieldWanted(attributeId, vcfFieldPatterns)) + { + vcfFieldsOfInterest.add(attributeId); + } + } + + FeatureSources.getInstance().addSource(theSourceId, metadata); + } + + /** + * Answers true if the field id is matched by any of the filter patterns, else + * false. Matching is against regular expression patterns, and is not + * case-sensitive. + * + * @param id + * @param filters + * @return + */ + private boolean isFieldWanted(String id, List filters) + { + for (Pattern p : filters) + { + if (p.matcher(id.toUpperCase()).matches()) + { + return true; + } + } + return false; + } + + /** + * Records 'wanted' fields defined in the CSQ INFO header (if there is one). + * Also records the position of selected fields (Allele, ALLELE_NUM, Feature) + * required for processing. + *

            + * CSQ fields are declared in the CSQ INFO Description e.g. + *

            + * Description="Consequence ...from ... VEP. Format: Allele|Consequence|... + */ + protected void parseCsqHeader() + { + List vepFieldFilters = getFieldMatchers(VEP_FIELDS_PREF, + DEFAULT_VEP_FIELDS); + vepFieldsOfInterest = new HashMap<>(); + + VCFInfoHeaderLine csqInfo = header.getInfoHeaderLine(CSQ_FIELD); + if (csqInfo == null) + { + return; + } + + /* + * parse out the pipe-separated list of CSQ fields; we assume here that + * these form the last part of the description, and contain no spaces + */ + String desc = csqInfo.getDescription(); + int spacePos = desc.lastIndexOf(" "); + desc = desc.substring(spacePos + 1); + + if (desc != null) + { + String[] format = desc.split(PIPE_REGEX); + int index = 0; + for (String field : format) + { + if (CSQ_CONSEQUENCE_KEY.equals(field)) + { + csqConsequenceFieldIndex = index; + } + if (CSQ_ALLELE_NUM_KEY.equals(field)) + { + csqAlleleNumberFieldIndex = index; + } + if (CSQ_ALLELE_KEY.equals(field)) + { + csqAlleleFieldIndex = index; + } + if (CSQ_FEATURE_KEY.equals(field)) + { + csqFeatureFieldIndex = index; + } + + if (isFieldWanted(field, vepFieldFilters)) + { + vepFieldsOfInterest.put(index, field); + } + + index++; + } + } + } + + /** + * Reads the Preference value for the given key, with default specified if no + * preference set. The value is interpreted as a comma-separated list of + * regular expressions, and converted into a list of compiled patterns ready + * for matching. Patterns are forced to upper-case for non-case-sensitive + * matching. + *

            + * This supports user-defined filters for fields of interest to capture while + * processing data. For example, VCF_FIELDS = AF,AC* would mean that VCF INFO + * fields with an ID of AF, or starting with AC, would be matched. + * + * @param key + * @param def + * @return + */ + private List getFieldMatchers(String key, String def) + { + String pref = Cache.getDefault(key, def); + List patterns = new ArrayList<>(); + String[] tokens = pref.split(","); + for (String token : tokens) + { + try + { + patterns.add(Pattern.compile(token.toUpperCase())); + } catch (PatternSyntaxException e) + { + System.err.println("Invalid pattern ignored: " + token); + } + } + return patterns; + } + + /** + * Transfers VCF features to sequences to which this sequence has a mapping. + * If the mapping is 3:1, computes peptide variants from nucleotide variants. + * + * @param seq + */ + protected void transferAddedFeatures(SequenceI seq) + { + DBRefEntry[] dbrefs = seq.getDBRefs(); + if (dbrefs == null) + { + return; + } + for (DBRefEntry dbref : dbrefs) + { + Mapping mapping = dbref.getMap(); + if (mapping == null || mapping.getTo() == null) + { + continue; + } + + SequenceI mapTo = mapping.getTo(); + MapList map = mapping.getMap(); + if (map.getFromRatio() == 3) + { + /* + * dna-to-peptide product mapping + */ + AlignmentUtils.computeProteinFeatures(seq, mapTo, map); + } + else + { + /* + * nucleotide-to-nucleotide mapping e.g. transcript to CDS + */ + List features = seq.getFeatures() + .getPositionalFeatures(SequenceOntologyI.SEQUENCE_VARIANT); + for (SequenceFeature sf : features) + { + if (FEATURE_GROUP_VCF.equals(sf.getFeatureGroup())) + { + transferFeature(sf, mapTo, map); + } + } + } + } + } + + /** + * Tries to add overlapping variants read from a VCF file to the given sequence, + * and returns the number of variant features added + * + * @param seq + * @param vcfAssembly + * @return + */ + protected int loadSequenceVCF(SequenceI seq, String vcfAssembly) + { + VCFMap vcfMap = getVcfMap(seq, vcfAssembly); + if (vcfMap == null) + { + return 0; + } + + /* + * work with the dataset sequence here + */ + SequenceI dss = seq.getDatasetSequence(); + if (dss == null) + { + dss = seq; + } + return addVcfVariants(dss, vcfMap); + } + + /** + * Answers a map from sequence coordinates to VCF chromosome ranges + * + * @param seq + * @param vcfAssembly + * @return + */ + private VCFMap getVcfMap(SequenceI seq, String vcfAssembly) + { + /* + * simplest case: sequence has id and length matching a VCF contig + */ + VCFMap vcfMap = null; + if (dictionary != null) + { + vcfMap = getContigMap(seq); + } + if (vcfMap != null) + { + return vcfMap; + } + + /* + * otherwise, map to VCF from chromosomal coordinates + * of the sequence (if known) + */ + GeneLociI seqCoords = seq.getGeneLoci(); + if (seqCoords == null) + { + Cache.log.warn(String.format( + "Can't query VCF for %s as chromosome coordinates not known", + seq.getName())); + return null; + } + + String species = seqCoords.getSpeciesId(); + String chromosome = seqCoords.getChromosomeId(); + String seqRef = seqCoords.getAssemblyId(); + MapList map = seqCoords.getMap(); + + if (!vcfSpeciesMatchesSequence(vcfAssembly, species)) + { + return null; + } + + if (vcfAssemblyMatchesSequence(vcfAssembly, seqRef)) + { + return new VCFMap(chromosome, map); + } + + if (!"GRCh38".equalsIgnoreCase(seqRef) // Ensembl + || !vcfAssembly.contains("Homo_sapiens_assembly19")) // gnomAD + { + return null; + } + + /* + * map chromosomal coordinates from sequence to VCF if the VCF + * data has a different reference assembly to the sequence + */ + // TODO generalise for cases other than GRCh38 -> GRCh37 ! + // - or get the user to choose in a dialog + + List toVcfRanges = new ArrayList<>(); + List fromSequenceRanges = new ArrayList<>(); + String toRef = "GRCh37"; + + for (int[] range : map.getToRanges()) + { + int[] fromRange = map.locateInFrom(range[0], range[1]); + if (fromRange == null) + { + // corrupted map?!? + continue; + } + + int[] newRange = mapReferenceRange(range, chromosome, "human", seqRef, + toRef); + if (newRange == null) + { + Cache.log.error( + String.format("Failed to map %s:%s:%s:%d:%d to %s", species, + chromosome, seqRef, range[0], range[1], toRef)); + continue; + } + else + { + toVcfRanges.add(newRange); + fromSequenceRanges.add(fromRange); + } + } + + return new VCFMap(chromosome, + new MapList(fromSequenceRanges, toVcfRanges, 1, 1)); + } + + /** + * If the sequence id matches a contig declared in the VCF file, and the + * sequence length matches the contig length, then returns a 1:1 map of the + * sequence to the contig, else returns null + * + * @param seq + * @return + */ + private VCFMap getContigMap(SequenceI seq) + { + String id = seq.getName(); + SAMSequenceRecord contig = dictionary.getSequence(id); + if (contig != null) + { + int len = seq.getLength(); + if (len == contig.getSequenceLength()) + { + MapList map = new MapList(new int[] { 1, len }, + new int[] + { 1, len }, 1, 1); + return new VCFMap(id, map); + } + } + return null; + } + + /** + * Answers true if we determine that the VCF data uses the same reference + * assembly as the sequence, else false + * + * @param vcfAssembly + * @param seqRef + * @return + */ + private boolean vcfAssemblyMatchesSequence(String vcfAssembly, + String seqRef) + { + // TODO improve on this stub, which handles gnomAD and + // hopes for the best for other cases + + if ("GRCh38".equalsIgnoreCase(seqRef) // Ensembl + && vcfAssembly.contains("Homo_sapiens_assembly19")) // gnomAD + { + return false; + } + return true; + } + + /** + * Answers true if the species inferred from the VCF reference identifier + * matches that for the sequence + * + * @param vcfAssembly + * @param speciesId + * @return + */ + boolean vcfSpeciesMatchesSequence(String vcfAssembly, String speciesId) + { + // PROBLEM 1 + // there are many aliases for species - how to equate one with another? + // PROBLEM 2 + // VCF ##reference header is an unstructured URI - how to extract species? + // perhaps check if ref includes any (Ensembl) alias of speciesId?? + // TODO ask the user to confirm this?? + + if (vcfAssembly.contains("Homo_sapiens") // gnomAD exome data example + && "HOMO_SAPIENS".equals(speciesId)) // Ensembl species id + { + return true; + } + + if (vcfAssembly.contains("c_elegans") // VEP VCF response example + && "CAENORHABDITIS_ELEGANS".equals(speciesId)) // Ensembl + { + return true; + } + + // this is not a sustainable solution... + + return false; + } + + /** + * Queries the VCF reader for any variants that overlap the mapped chromosome + * ranges of the sequence, and adds as variant features. Returns the number of + * overlapping variants found. + * + * @param seq + * @param map + * mapping from sequence to VCF coordinates + * @return + */ + protected int addVcfVariants(SequenceI seq, VCFMap map) + { + boolean forwardStrand = map.map.isToForwardStrand(); + + /* + * query the VCF for overlaps of each contiguous chromosomal region + */ + int count = 0; + + for (int[] range : map.map.getToRanges()) + { + int vcfStart = Math.min(range[0], range[1]); + int vcfEnd = Math.max(range[0], range[1]); + CloseableIterator variants = reader + .query(map.chromosome, vcfStart, vcfEnd); + while (variants.hasNext()) + { + VariantContext variant = variants.next(); + + int[] featureRange = map.map.locateInFrom(variant.getStart(), + variant.getEnd()); + + if (featureRange != null) + { + int featureStart = Math.min(featureRange[0], featureRange[1]); + int featureEnd = Math.max(featureRange[0], featureRange[1]); + count += addAlleleFeatures(seq, variant, featureStart, featureEnd, + forwardStrand); + } + } + variants.close(); + } + + return count; + } + + /** + * A convenience method to get the AF value for the given alternate allele + * index + * + * @param variant + * @param alleleIndex + * @return + */ + protected float getAlleleFrequency(VariantContext variant, int alleleIndex) + { + float score = 0f; + String attributeValue = getAttributeValue(variant, + ALLELE_FREQUENCY_KEY, alleleIndex); + if (attributeValue != null) + { + try + { + score = Float.parseFloat(attributeValue); + } catch (NumberFormatException e) + { + // leave as 0 + } + } + + return score; + } + + /** + * A convenience method to get an attribute value for an alternate allele + * + * @param variant + * @param attributeName + * @param alleleIndex + * @return + */ + protected String getAttributeValue(VariantContext variant, + String attributeName, int alleleIndex) + { + Object att = variant.getAttribute(attributeName); + + if (att instanceof String) + { + return (String) att; + } + else if (att instanceof ArrayList) + { + return ((List) att).get(alleleIndex); + } + + return null; + } + + /** + * Adds one variant feature for each allele in the VCF variant record, and + * returns the number of features added. + * + * @param seq + * @param variant + * @param featureStart + * @param featureEnd + * @param forwardStrand + * @return + */ + protected int addAlleleFeatures(SequenceI seq, VariantContext variant, + int featureStart, int featureEnd, boolean forwardStrand) + { + int added = 0; + + /* + * Javadoc says getAlternateAlleles() imposes no order on the list returned + * so we proceed defensively to get them in strict order + */ + int altAlleleCount = variant.getAlternateAlleles().size(); + for (int i = 0; i < altAlleleCount; i++) + { + added += addAlleleFeature(seq, variant, i, featureStart, featureEnd, + forwardStrand); + } + return added; + } + + /** + * Inspects one allele and attempts to add a variant feature for it to the + * sequence. The additional data associated with this allele is extracted to + * store in the feature's key-value map. Answers the number of features added (0 + * or 1). + * + * @param seq + * @param variant + * @param altAlleleIndex + * (0, 1..) + * @param featureStart + * @param featureEnd + * @param forwardStrand + * @return + */ + protected int addAlleleFeature(SequenceI seq, VariantContext variant, + int altAlleleIndex, int featureStart, int featureEnd, + boolean forwardStrand) + { + String reference = variant.getReference().getBaseString(); + Allele alt = variant.getAlternateAllele(altAlleleIndex); + String allele = alt.getBaseString(); + + /* + * insertion after a genomic base, if on reverse strand, has to be + * converted to insertion of complement after the preceding position + */ + int referenceLength = reference.length(); + if (!forwardStrand && allele.length() > referenceLength + && allele.startsWith(reference)) + { + featureStart -= referenceLength; + featureEnd = featureStart; + char insertAfter = seq.getCharAt(featureStart - seq.getStart()); + reference = Dna.reverseComplement(String.valueOf(insertAfter)); + allele = allele.substring(referenceLength) + reference; + } + + /* + * build the ref,alt allele description e.g. "G,A", using the base + * complement if the sequence is on the reverse strand + */ + StringBuilder sb = new StringBuilder(); + sb.append(forwardStrand ? reference : Dna.reverseComplement(reference)); + sb.append(COMMA); + sb.append(forwardStrand ? allele : Dna.reverseComplement(allele)); + String alleles = sb.toString(); // e.g. G,A + + /* + * pick out the consequence data (if any) that is for the current allele + * and feature (transcript) that matches the current sequence + */ + String consequence = getConsequenceForAlleleAndFeature(variant, CSQ_FIELD, + altAlleleIndex, csqAlleleFieldIndex, + csqAlleleNumberFieldIndex, seq.getName().toLowerCase(), + csqFeatureFieldIndex); + + /* + * pick out the ontology term for the consequence type + */ + String type = SequenceOntologyI.SEQUENCE_VARIANT; + if (consequence != null) + { + type = getOntologyTerm(consequence); + } + + float score = getAlleleFrequency(variant, altAlleleIndex); + + SequenceFeature sf = new SequenceFeature(type, alleles, featureStart, + featureEnd, score, FEATURE_GROUP_VCF); + sf.setSource(sourceId); + + sf.setValue(Gff3Helper.ALLELES, alleles); + + addAlleleProperties(variant, sf, altAlleleIndex, consequence); + + seq.addSequenceFeature(sf); + + return 1; + } + + /** + * Determines the Sequence Ontology term to use for the variant feature type in + * Jalview. The default is 'sequence_variant', but a more specific term is used + * if: + *

              + *
            • VEP (or SnpEff) Consequence annotation is included in the VCF
            • + *
            • sequence id can be matched to VEP Feature (or SnpEff Feature_ID)
            • + *
            + * + * @param consequence + * @return + * @see http://www.sequenceontology.org/browser/current_svn/term/SO:0001060 + */ + String getOntologyTerm(String consequence) + { + String type = SequenceOntologyI.SEQUENCE_VARIANT; + + /* + * could we associate Consequence data with this allele and feature (transcript)? + * if so, prefer the consequence term from that data + */ + if (csqAlleleFieldIndex == -1) // && snpEffAlleleFieldIndex == -1 + { + /* + * no Consequence data so we can't refine the ontology term + */ + return type; + } + + if (consequence != null) + { + String[] csqFields = consequence.split(PIPE_REGEX); + if (csqFields.length > csqConsequenceFieldIndex) + { + type = csqFields[csqConsequenceFieldIndex]; + } + } + else + { + // todo the same for SnpEff consequence data matching if wanted + } + + /* + * if of the form (e.g.) missense_variant&splice_region_variant, + * just take the first ('most severe') consequence + */ + if (type != null) + { + int pos = type.indexOf('&'); + if (pos > 0) + { + type = type.substring(0, pos); + } + } + return type; + } + + /** + * Returns matched consequence data if it can be found, else null. + *
              + *
            • inspects the VCF data for key 'vcfInfoId'
            • + *
            • splits this on comma (to distinct consequences)
            • + *
            • returns the first consequence (if any) where
            • + *
                + *
              • the allele matches the altAlleleIndex'th allele of variant
              • + *
              • the feature matches the sequence name (e.g. transcript id)
              • + *
              + *
            + * If matched, the consequence is returned (as pipe-delimited fields). + * + * @param variant + * @param vcfInfoId + * @param altAlleleIndex + * @param alleleFieldIndex + * @param alleleNumberFieldIndex + * @param seqName + * @param featureFieldIndex + * @return + */ + private String getConsequenceForAlleleAndFeature(VariantContext variant, + String vcfInfoId, int altAlleleIndex, int alleleFieldIndex, + int alleleNumberFieldIndex, + String seqName, int featureFieldIndex) + { + if (alleleFieldIndex == -1 || featureFieldIndex == -1) + { + return null; + } + Object value = variant.getAttribute(vcfInfoId); + + if (value == null || !(value instanceof List)) + { + return null; + } + + /* + * inspect each consequence in turn (comma-separated blocks + * extracted by htsjdk) + */ + List consequences = (List) value; + + for (String consequence : consequences) + { + String[] csqFields = consequence.split(PIPE_REGEX); + if (csqFields.length > featureFieldIndex) + { + String featureIdentifier = csqFields[featureFieldIndex]; + if (featureIdentifier.length() > 4 + && seqName.indexOf(featureIdentifier.toLowerCase()) > -1) + { + /* + * feature (transcript) matched - now check for allele match + */ + if (matchAllele(variant, altAlleleIndex, csqFields, + alleleFieldIndex, alleleNumberFieldIndex)) + { + return consequence; + } + } + } + } + return null; + } + + private boolean matchAllele(VariantContext variant, int altAlleleIndex, + String[] csqFields, int alleleFieldIndex, + int alleleNumberFieldIndex) + { + /* + * if ALLELE_NUM is present, it must match altAlleleIndex + * NB first alternate allele is 1 for ALLELE_NUM, 0 for altAlleleIndex + */ + if (alleleNumberFieldIndex > -1) + { + if (csqFields.length <= alleleNumberFieldIndex) + { + return false; + } + String alleleNum = csqFields[alleleNumberFieldIndex]; + return String.valueOf(altAlleleIndex + 1).equals(alleleNum); + } + + /* + * else consequence allele must match variant allele + */ + if (alleleFieldIndex > -1 && csqFields.length > alleleFieldIndex) + { + String csqAllele = csqFields[alleleFieldIndex]; + String vcfAllele = variant.getAlternateAllele(altAlleleIndex) + .getBaseString(); + return csqAllele.equals(vcfAllele); + } + return false; + } + + /** + * Add any allele-specific VCF key-value data to the sequence feature + * + * @param variant + * @param sf + * @param altAlelleIndex + * (0, 1..) + * @param consequence + * if not null, the consequence specific to this sequence (transcript + * feature) and allele + */ + protected void addAlleleProperties(VariantContext variant, + SequenceFeature sf, final int altAlelleIndex, String consequence) + { + Map atts = variant.getAttributes(); + + for (Entry att : atts.entrySet()) + { + String key = att.getKey(); + + /* + * extract Consequence data (if present) that we are able to + * associated with the allele for this variant feature + */ + if (CSQ_FIELD.equals(key)) + { + addConsequences(variant, sf, consequence); + continue; + } + + /* + * filter out fields we don't want to capture + */ + if (!vcfFieldsOfInterest.contains(key)) + { + continue; + } + + /* + * filter out fields we don't want to capture + */ + if (!vcfFieldsOfInterest.contains(key)) + { + continue; + } + + /* + * we extract values for other data which are allele-specific; + * these may be per alternate allele (INFO[key].Number = 'A') + * or per allele including reference (INFO[key].Number = 'R') + */ + VCFInfoHeaderLine infoHeader = header.getInfoHeaderLine(key); + if (infoHeader == null) + { + /* + * can't be sure what data belongs to this allele, so + * play safe and don't take any + */ + continue; + } + + VCFHeaderLineCount number = infoHeader.getCountType(); + int index = altAlelleIndex; + if (number == VCFHeaderLineCount.R) + { + /* + * one value per allele including reference, so bump index + * e.g. the 3rd value is for the 2nd alternate allele + */ + index++; + } + else if (number != VCFHeaderLineCount.A) + { + /* + * don't save other values as not allele-related + */ + continue; + } + + /* + * take the index'th value + */ + String value = getAttributeValue(variant, key, index); + if (value != null) + { + sf.setValue(key, value); + } + } + } + + /** + * Inspects CSQ data blocks (consequences) and adds attributes on the sequence + * feature. + *

            + * If myConsequence is not null, then this is the specific + * consequence data (pipe-delimited fields) that is for the current allele and + * transcript (sequence) being processed) + * + * @param variant + * @param sf + * @param myConsequence + */ + protected void addConsequences(VariantContext variant, SequenceFeature sf, + String myConsequence) + { + Object value = variant.getAttribute(CSQ_FIELD); + + if (value == null || !(value instanceof List)) + { + return; + } + + List consequences = (List) value; + + /* + * inspect CSQ consequences; restrict to the consequence + * associated with the current transcript (Feature) + */ + Map csqValues = new HashMap<>(); + + for (String consequence : consequences) + { + if (myConsequence == null || myConsequence.equals(consequence)) + { + String[] csqFields = consequence.split(PIPE_REGEX); + + /* + * inspect individual fields of this consequence, copying non-null + * values which are 'fields of interest' + */ + int i = 0; + for (String field : csqFields) + { + if (field != null && field.length() > 0) + { + String id = vepFieldsOfInterest.get(i); + if (id != null) + { + csqValues.put(id, field); + } + } + i++; + } + } + } + + if (!csqValues.isEmpty()) + { + sf.setValue(CSQ_FIELD, csqValues); + } + } + + /** + * A convenience method to complement a dna base and return the string value + * of its complement + * + * @param reference + * @return + */ + protected String complement(byte[] reference) + { + return String.valueOf(Dna.getComplement((char) reference[0])); + } + + /** + * Determines the location of the query range (chromosome positions) in a + * different reference assembly. + *

            + * If the range is just a subregion of one for which we already have a mapping + * (for example, an exon sub-region of a gene), then the mapping is just + * computed arithmetically. + *

            + * Otherwise, calls the Ensembl REST service that maps from one assembly + * reference's coordinates to another's + * + * @param queryRange + * start-end chromosomal range in 'fromRef' coordinates + * @param chromosome + * @param species + * @param fromRef + * assembly reference for the query coordinates + * @param toRef + * assembly reference we wish to translate to + * @return the start-end range in 'toRef' coordinates + */ + protected int[] mapReferenceRange(int[] queryRange, String chromosome, + String species, String fromRef, String toRef) + { + /* + * first try shorcut of computing the mapping as a subregion of one + * we already have (e.g. for an exon, if we have the gene mapping) + */ + int[] mappedRange = findSubsumedRangeMapping(queryRange, chromosome, + species, fromRef, toRef); + if (mappedRange != null) + { + return mappedRange; + } + + /* + * call (e.g.) http://rest.ensembl.org/map/human/GRCh38/17:45051610..45109016:1/GRCh37 + */ + EnsemblMap mapper = new EnsemblMap(); + int[] mapping = mapper.getAssemblyMapping(species, chromosome, fromRef, + toRef, queryRange); + + if (mapping == null) + { + // mapping service failure + return null; + } + + /* + * save mapping for possible future re-use + */ + String key = makeRangesKey(chromosome, species, fromRef, toRef); + if (!assemblyMappings.containsKey(key)) + { + assemblyMappings.put(key, new HashMap()); + } + + assemblyMappings.get(key).put(queryRange, mapping); + + return mapping; + } + + /** + * If we already have a 1:1 contiguous mapping which subsumes the given query + * range, this method just calculates and returns the subset of that mapping, + * else it returns null. In practical terms, if a gene has a contiguous + * mapping between (for example) GRCh37 and GRCh38, then we assume that its + * subsidiary exons occupy unchanged relative positions, and just compute + * these as offsets, rather than do another lookup of the mapping. + *

            + * If in future these assumptions prove invalid (e.g. for bacterial dna?!), + * simply remove this method or let it always return null. + *

            + * Warning: many rapid calls to the /map service map result in a 429 overload + * error response + * + * @param queryRange + * @param chromosome + * @param species + * @param fromRef + * @param toRef + * @return + */ + protected int[] findSubsumedRangeMapping(int[] queryRange, String chromosome, + String species, String fromRef, String toRef) + { + String key = makeRangesKey(chromosome, species, fromRef, toRef); + if (assemblyMappings.containsKey(key)) + { + Map mappedRanges = assemblyMappings.get(key); + for (Entry mappedRange : mappedRanges.entrySet()) + { + int[] fromRange = mappedRange.getKey(); + int[] toRange = mappedRange.getValue(); + if (fromRange[1] - fromRange[0] == toRange[1] - toRange[0]) + { + /* + * mapping is 1:1 in length, so we trust it to have no discontinuities + */ + if (MappingUtils.rangeContains(fromRange, queryRange)) + { + /* + * fromRange subsumes our query range + */ + int offset = queryRange[0] - fromRange[0]; + int mappedRangeFrom = toRange[0] + offset; + int mappedRangeTo = mappedRangeFrom + (queryRange[1] - queryRange[0]); + return new int[] { mappedRangeFrom, mappedRangeTo }; + } + } + } + } + return null; + } + + /** + * Transfers the sequence feature to the target sequence, locating its start + * and end range based on the mapping. Features which do not overlap the + * target sequence are ignored. + * + * @param sf + * @param targetSequence + * @param mapping + * mapping from the feature's coordinates to the target sequence + */ + protected void transferFeature(SequenceFeature sf, + SequenceI targetSequence, MapList mapping) + { + int[] mappedRange = mapping.locateInTo(sf.getBegin(), sf.getEnd()); + + if (mappedRange != null) + { + String group = sf.getFeatureGroup(); + int newBegin = Math.min(mappedRange[0], mappedRange[1]); + int newEnd = Math.max(mappedRange[0], mappedRange[1]); + SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd, + group, sf.getScore()); + targetSequence.addSequenceFeature(copy); + } + } + + /** + * Formats a ranges map lookup key + * + * @param chromosome + * @param species + * @param fromRef + * @param toRef + * @return + */ + protected static String makeRangesKey(String chromosome, String species, + String fromRef, String toRef) + { + return species + EXCL + chromosome + EXCL + fromRef + EXCL + + toRef; + } +} diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index 86d0c85..1cf482d 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -147,6 +147,8 @@ public class GAlignFrame extends JInternalFrame protected JMenuItem runGroovy = new JMenuItem(); + protected JMenuItem loadVcf; + protected JCheckBoxMenuItem autoCalculate = new JCheckBoxMenuItem(); protected JCheckBoxMenuItem sortByTree = new JCheckBoxMenuItem(); @@ -1308,6 +1310,16 @@ public class GAlignFrame extends JInternalFrame associatedData_actionPerformed(e); } }); + loadVcf = new JMenuItem(MessageManager.getString("label.load_vcf_file")); + loadVcf.setToolTipText(MessageManager.getString("label.load_vcf")); + loadVcf.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + loadVcf_actionPerformed(); + } + }); autoCalculate.setText( MessageManager.getString("label.autocalculate_consensus")); autoCalculate.setState( @@ -1710,6 +1722,7 @@ public class GAlignFrame extends JInternalFrame fileMenu.add(exportAnnotations); fileMenu.add(loadTreeMenuItem); fileMenu.add(associatedData); + fileMenu.add(loadVcf); fileMenu.addSeparator(); fileMenu.add(closeMenuItem); @@ -1855,6 +1868,10 @@ public class GAlignFrame extends JInternalFrame // selectMenu.add(listenToViewSelections); } + protected void loadVcf_actionPerformed() + { + } + /** * Constructs the entries on the Colour menu (but does not add them to the * menu). diff --git a/src/jalview/jbgui/GCutAndPasteHtmlTransfer.java b/src/jalview/jbgui/GCutAndPasteHtmlTransfer.java index abc0b3d..a6e0ace 100644 --- a/src/jalview/jbgui/GCutAndPasteHtmlTransfer.java +++ b/src/jalview/jbgui/GCutAndPasteHtmlTransfer.java @@ -39,6 +39,8 @@ import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.text.EditorKit; +import javax.swing.text.html.HTMLEditorKit; /** * DOCUMENT ME! @@ -85,6 +87,7 @@ public class GCutAndPasteHtmlTransfer extends JInternalFrame { try { + textarea.setEditorKit(new HTMLEditorKit()); setJMenuBar(editMenubar); jbInit(); } catch (Exception e) @@ -272,4 +275,20 @@ public class GCutAndPasteHtmlTransfer extends JInternalFrame { } + + /** + * Adds the given stylesheet rule to the Html editor. However note that CSS + * support is limited. + * + * @param rule + * @see javax.swing.text.html.CSS + */ + public void addStylesheetRule(String rule) + { + EditorKit editorKit = textarea.getEditorKit(); + if (editorKit != null) + { + ((HTMLEditorKit) editorKit).getStyleSheet().addRule(rule); + } + } } diff --git a/src/jalview/jbgui/GPreferences.java b/src/jalview/jbgui/GPreferences.java index 1ca0802..6807382 100755 --- a/src/jalview/jbgui/GPreferences.java +++ b/src/jalview/jbgui/GPreferences.java @@ -264,10 +264,6 @@ public class GPreferences extends JPanel protected JCheckBox sortByTree = new JCheckBox(); - /* - * DAS Settings tab - */ - protected JPanel dasTab = new JPanel(); /* * Web Services tab @@ -326,12 +322,6 @@ public class GPreferences extends JPanel MessageManager.getString("label.editing")); /* - * See DasSourceBrowser for the real work of configuring this tab. - */ - dasTab.setLayout(new BorderLayout()); - tabbedPane.add(dasTab, MessageManager.getString("label.das_settings")); - - /* * See WsPreferences for the real work of configuring this tab. */ wsTab.setLayout(new BorderLayout()); @@ -524,7 +514,9 @@ public class GPreferences extends JPanel MessageManager.getString("label.default_browser_unix")); defaultBrowser.setFont(LABEL_FONT); defaultBrowser.setText(""); - + final String tooltip = JvSwingUtils.wrapTooltip(true, + MessageManager.getString("label.double_click_to_browse")); + defaultBrowser.setToolTipText(tooltip); defaultBrowser.addMouseListener(new MouseAdapter() { @Override @@ -1206,14 +1198,14 @@ public class GPreferences extends JPanel pathLabel.setFont(new java.awt.Font("SansSerif", 0, 11)); pathLabel.setHorizontalAlignment(SwingConstants.LEFT); pathLabel.setText(MessageManager.getString("label.chimera_path")); - final String tooltip = JvSwingUtils.wrapTooltip(true, - MessageManager.getString("label.chimera_path_tip")); - pathLabel.setToolTipText(tooltip); pathLabel.setBounds(new Rectangle(10, ypos, 140, height)); structureTab.add(pathLabel); chimeraPath.setFont(LABEL_FONT); chimeraPath.setText(""); + final String tooltip = JvSwingUtils.wrapTooltip(true, + MessageManager.getString("label.chimera_path_tip")); + chimeraPath.setToolTipText(tooltip); chimeraPath.setBounds(new Rectangle(160, ypos, 300, height)); chimeraPath.addMouseListener(new MouseAdapter() { @@ -1512,6 +1504,9 @@ public class GPreferences extends JPanel startupCheckbox.setSelected(true); startupFileTextfield.setFont(LABEL_FONT); startupFileTextfield.setBounds(new Rectangle(172, 310, 330, 20)); + final String tooltip = JvSwingUtils.wrapTooltip(true, + MessageManager.getString("label.double_click_to_browse")); + startupFileTextfield.setToolTipText(tooltip); startupFileTextfield.addMouseListener(new MouseAdapter() { @Override diff --git a/src/jalview/jbgui/GStructureChooser.java b/src/jalview/jbgui/GStructureChooser.java index 7c4672a..240e1fd 100644 --- a/src/jalview/jbgui/GStructureChooser.java +++ b/src/jalview/jbgui/GStructureChooser.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2) - * Copyright (C) 2014 The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * @@ -29,6 +29,7 @@ import jalview.fts.service.pdb.PDBFTSRestClient; import jalview.gui.AlignmentPanel; import jalview.gui.Desktop; import jalview.gui.JvSwingUtils; +import jalview.gui.StructureViewer; import jalview.util.MessageManager; import java.awt.BorderLayout; @@ -36,6 +37,7 @@ import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; @@ -70,6 +72,8 @@ import javax.swing.event.DocumentListener; import javax.swing.event.InternalFrameEvent; import javax.swing.table.TableColumn; +import net.miginfocom.swing.MigLayout; + @SuppressWarnings("serial") /** * GUI layout for structure chooser @@ -80,58 +84,50 @@ import javax.swing.table.TableColumn; public abstract class GStructureChooser extends JPanel implements ItemListener { + private static final Font VERDANA_12 = new Font("Verdana", 0, 12); + + protected static final String VIEWS_FILTER = "VIEWS_FILTER"; + + protected static final String VIEWS_FROM_FILE = "VIEWS_FROM_FILE"; + + protected static final String VIEWS_ENTER_ID = "VIEWS_ENTER_ID"; + + /* + * 'cached' structure view + */ + protected static final String VIEWS_LOCAL_PDB = "VIEWS_LOCAL_PDB"; + protected JPanel statusPanel = new JPanel(); public JLabel statusBar = new JLabel(); - private JPanel pnl_actionsAndStatus = new JPanel(new BorderLayout()); - protected String frameTitle = MessageManager .getString("label.structure_chooser"); protected JInternalFrame mainFrame = new JInternalFrame(frameTitle); - protected JComboBox cmb_filterOption = new JComboBox(); + protected JComboBox cmb_filterOption = new JComboBox<>(); protected AlignmentPanel ap; protected StringBuilder errorWarning = new StringBuilder(); - protected JLabel lbl_result = new JLabel( - MessageManager.getString("label.select")); - - protected JButton btn_view = new JButton(); + protected JButton btn_add; - protected JButton btn_cancel = new JButton(); + protected JButton btn_newView; protected JButton btn_pdbFromFile = new JButton(); - protected JTextField txt_search = new JTextField(14); - - private JPanel pnl_actions = new JPanel(); - - private JPanel pnl_main = new JPanel(); - - private JPanel pnl_idInput = new JPanel(new FlowLayout()); + protected JCheckBox chk_superpose = new JCheckBox( + MessageManager.getString("label.superpose_structures")); - private JPanel pnl_fileChooser = new JPanel(new FlowLayout()); - - private JPanel pnl_idInputBL = new JPanel(new BorderLayout()); - - private JPanel pnl_fileChooserBL = new JPanel(new BorderLayout()); - - private JPanel pnl_locPDB = new JPanel(new BorderLayout()); + protected JTextField txt_search = new JTextField(14); protected JPanel pnl_switchableViews = new JPanel(new CardLayout()); protected CardLayout layout_switchableViews = (CardLayout) (pnl_switchableViews .getLayout()); - private BorderLayout mainLayout = new BorderLayout(); - - protected JCheckBox chk_rememberSettings = new JCheckBox( - MessageManager.getString("label.dont_ask_me_again")); - protected JCheckBox chk_invertFilter = new JCheckBox( MessageManager.getString("label.invert")); @@ -147,33 +143,20 @@ public abstract class GStructureChooser extends JPanel protected ImageIcon warningImage = new ImageIcon( getClass().getResource("/images/warning.gif")); - protected JLabel lbl_warning = new JLabel(warningImage); - protected JLabel lbl_loading = new JLabel(loadingImage); protected JLabel lbl_pdbManualFetchStatus = new JLabel(errorImage); protected JLabel lbl_fromFileStatus = new JLabel(errorImage); - protected AssciateSeqPanel idInputAssSeqPanel = new AssciateSeqPanel(); - - protected AssciateSeqPanel fileChooserAssSeqPanel = new AssciateSeqPanel(); - - protected static final String VIEWS_FILTER = "VIEWS_FILTER"; + protected AssociateSeqPanel idInputAssSeqPanel = new AssociateSeqPanel(); - protected static final String VIEWS_FROM_FILE = "VIEWS_FROM_FILE"; + protected AssociateSeqPanel fileChooserAssSeqPanel = new AssociateSeqPanel(); - protected static final String VIEWS_ENTER_ID = "VIEWS_ENTER_ID"; - - /** - * 'cached' structure view - */ - protected static final String VIEWS_LOCAL_PDB = "VIEWS_LOCAL_PDB"; + protected JComboBox targetView = new JComboBox<>(); protected JTable tbl_local_pdb = new JTable(); - protected JScrollPane scrl_localPDB = new JScrollPane(tbl_local_pdb); - protected JTabbedPane pnl_filter = new JTabbedPane(); protected FTSDataColumnPreferences pdbDocFieldPrefs = new FTSDataColumnPreferences( @@ -182,7 +165,7 @@ public abstract class GStructureChooser extends JPanel protected FTSDataColumnI[] previousWantedFields; - protected static Map tempUserPrefs = new HashMap(); + protected static Map tempUserPrefs = new HashMap<>(); private JTable tbl_summary = new JTable() { @@ -262,8 +245,6 @@ public abstract class GStructureChooser extends JPanel } }; - protected JScrollPane scrl_foundStructures = new JScrollPane(tbl_summary); - public GStructureChooser() { try @@ -319,9 +300,9 @@ public abstract class GStructureChooser extends JPanel mainFrame.dispose(); break; case KeyEvent.VK_ENTER: // enter key - if (btn_view.isEnabled()) + if (btn_add.isEnabled()) { - ok_ActionPerformed(); + add_ActionPerformed(); } break; case KeyEvent.VK_TAB: // tab key @@ -331,7 +312,7 @@ public abstract class GStructureChooser extends JPanel } else { - btn_view.requestFocus(); + btn_add.requestFocus(); } evt.consume(); break; @@ -340,6 +321,30 @@ public abstract class GStructureChooser extends JPanel } } }); + + JButton btn_cancel = new JButton( + MessageManager.getString("action.cancel")); + btn_cancel.setFont(VERDANA_12); + btn_cancel.addActionListener(new java.awt.event.ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + closeAction(pnl_filter.getHeight()); + } + }); + btn_cancel.addKeyListener(new KeyAdapter() + { + @Override + public void keyPressed(KeyEvent evt) + { + if (evt.getKeyCode() == KeyEvent.VK_ENTER) + { + closeAction(pnl_filter.getHeight()); + } + } + }); + tbl_local_pdb.setAutoCreateRowSorter(true); tbl_local_pdb.getTableHeader().setReorderingAllowed(false); tbl_local_pdb.addMouseListener(new MouseAdapter() @@ -368,9 +373,9 @@ public abstract class GStructureChooser extends JPanel mainFrame.dispose(); break; case KeyEvent.VK_ENTER: // enter key - if (btn_view.isEnabled()) + if (btn_add.isEnabled()) { - ok_ActionPerformed(); + add_ActionPerformed(); } break; case KeyEvent.VK_TAB: // tab key @@ -380,9 +385,9 @@ public abstract class GStructureChooser extends JPanel } else { - if (btn_view.isEnabled()) + if (btn_add.isEnabled()) { - btn_view.requestFocus(); + btn_add.requestFocus(); } else { @@ -396,51 +401,52 @@ public abstract class GStructureChooser extends JPanel } } }); - btn_view.setFont(new java.awt.Font("Verdana", 0, 12)); - btn_view.setText(MessageManager.getString("action.view")); - btn_view.addActionListener(new java.awt.event.ActionListener() + + btn_newView = new JButton(MessageManager.getString("action.new_view")); + btn_newView.setFont(VERDANA_12); + btn_newView.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(ActionEvent e) { - ok_ActionPerformed(); + newView_ActionPerformed(); } }); - btn_view.addKeyListener(new KeyAdapter() + btn_newView.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent evt) { if (evt.getKeyCode() == KeyEvent.VK_ENTER) { - ok_ActionPerformed(); + newView_ActionPerformed(); } } }); - btn_cancel.setFont(new java.awt.Font("Verdana", 0, 12)); - btn_cancel.setText(MessageManager.getString("action.cancel")); - btn_cancel.addActionListener(new java.awt.event.ActionListener() + btn_add = new JButton(MessageManager.getString("action.add")); + btn_add.setFont(VERDANA_12); + btn_add.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(ActionEvent e) { - closeAction(pnl_filter.getHeight()); + add_ActionPerformed(); } }); - btn_cancel.addKeyListener(new KeyAdapter() + btn_add.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent evt) { if (evt.getKeyCode() == KeyEvent.VK_ENTER) { - closeAction(pnl_filter.getHeight()); + add_ActionPerformed(); } } }); - btn_pdbFromFile.setFont(new java.awt.Font("Verdana", 0, 12)); + btn_pdbFromFile.setFont(VERDANA_12); String btn_title = MessageManager.getString("label.select_pdb_file"); btn_pdbFromFile.setText(btn_title + " "); btn_pdbFromFile.addActionListener(new java.awt.event.ActionListener() @@ -463,20 +469,17 @@ public abstract class GStructureChooser extends JPanel } }); + JScrollPane scrl_foundStructures = new JScrollPane(tbl_summary); scrl_foundStructures.setPreferredSize(new Dimension(width, height)); + JScrollPane scrl_localPDB = new JScrollPane(tbl_local_pdb); scrl_localPDB.setPreferredSize(new Dimension(width, height)); scrl_localPDB.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - cmb_filterOption.setFont(new java.awt.Font("Verdana", 0, 12)); - chk_invertFilter.setFont(new java.awt.Font("Verdana", 0, 12)); - chk_rememberSettings.setFont(new java.awt.Font("Verdana", 0, 12)); - chk_rememberSettings.setVisible(false); + chk_invertFilter.setFont(VERDANA_12); txt_search.setToolTipText(JvSwingUtils.wrapTooltip(true, - MessageManager.getString("label.enter_pdb_id"))); - cmb_filterOption.setToolTipText( - MessageManager.getString("info.select_filter_option")); + MessageManager.getString("label.enter_pdb_id_tip"))); txt_search.getDocument().addDocumentListener(new DocumentListener() { @Override @@ -498,8 +501,10 @@ public abstract class GStructureChooser extends JPanel } }); + cmb_filterOption.setFont(VERDANA_12); + cmb_filterOption.setToolTipText( + MessageManager.getString("info.select_filter_option")); cmb_filterOption.addItemListener(this); - // add CustomComboSeparatorsRenderer to filter option combo-box cmb_filterOption.setRenderer(new CustomComboSeparatorsRenderer( (ListCellRenderer) cmb_filterOption.getRenderer()) @@ -514,23 +519,33 @@ public abstract class GStructureChooser extends JPanel chk_invertFilter.addItemListener(this); - pnl_actions.add(chk_rememberSettings); - pnl_actions.add(btn_view); - pnl_actions.add(btn_cancel); + targetView.setVisible(false); - // pnl_filter.add(lbl_result); + JPanel actionsPanel = new JPanel(new MigLayout()); + actionsPanel.add(targetView, "left"); + actionsPanel.add(btn_add, "wrap"); + actionsPanel.add(chk_superpose, "left"); + actionsPanel.add(btn_newView); + actionsPanel.add(btn_cancel, "right"); + + JPanel pnl_main = new JPanel(); pnl_main.add(cmb_filterOption); pnl_main.add(lbl_loading); pnl_main.add(chk_invertFilter); lbl_loading.setVisible(false); + JPanel pnl_fileChooser = new JPanel(new FlowLayout()); pnl_fileChooser.add(btn_pdbFromFile); pnl_fileChooser.add(lbl_fromFileStatus); + JPanel pnl_fileChooserBL = new JPanel(new BorderLayout()); pnl_fileChooserBL.add(fileChooserAssSeqPanel, BorderLayout.NORTH); pnl_fileChooserBL.add(pnl_fileChooser, BorderLayout.CENTER); + JPanel pnl_idInput = new JPanel(new FlowLayout()); pnl_idInput.add(txt_search); pnl_idInput.add(lbl_pdbManualFetchStatus); + + JPanel pnl_idInputBL = new JPanel(new BorderLayout()); pnl_idInputBL.add(idInputAssSeqPanel, BorderLayout.NORTH); pnl_idInputBL.add(pnl_idInput, BorderLayout.CENTER); @@ -546,13 +561,15 @@ public abstract class GStructureChooser extends JPanel JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent .getSource(); int index = sourceTabbedPane.getSelectedIndex(); - btn_view.setVisible(true); + btn_add.setVisible(targetView.isVisible()); + btn_newView.setVisible(true); btn_cancel.setVisible(true); if (sourceTabbedPane.getTitleAt(index).equals(configureCols)) { - btn_view.setEnabled(false); + btn_add.setEnabled(false); btn_cancel.setEnabled(false); - btn_view.setVisible(false); + btn_add.setVisible(false); + btn_newView.setEnabled(false); btn_cancel.setVisible(false); previousWantedFields = pdbDocFieldPrefs .getStructureSummaryFields() @@ -578,6 +595,7 @@ public abstract class GStructureChooser extends JPanel pnl_filter.add(foundStructureSummary, scrl_foundStructures); pnl_filter.add(configureCols, pdbDocFieldPrefs); + JPanel pnl_locPDB = new JPanel(new BorderLayout()); pnl_locPDB.add(scrl_localPDB); pnl_switchableViews.add(pnl_fileChooserBL, VIEWS_FROM_FILE); @@ -585,12 +603,14 @@ public abstract class GStructureChooser extends JPanel pnl_switchableViews.add(pnl_filter, VIEWS_FILTER); pnl_switchableViews.add(pnl_locPDB, VIEWS_LOCAL_PDB); - this.setLayout(mainLayout); + this.setLayout(new BorderLayout()); this.add(pnl_main, java.awt.BorderLayout.NORTH); this.add(pnl_switchableViews, java.awt.BorderLayout.CENTER); // this.add(pnl_actions, java.awt.BorderLayout.SOUTH); statusPanel.setLayout(new GridLayout()); - pnl_actionsAndStatus.add(pnl_actions, BorderLayout.CENTER); + + JPanel pnl_actionsAndStatus = new JPanel(new BorderLayout()); + pnl_actionsAndStatus.add(actionsPanel, BorderLayout.CENTER); pnl_actionsAndStatus.add(statusPanel, BorderLayout.SOUTH); statusPanel.add(statusBar, null); this.add(pnl_actionsAndStatus, java.awt.BorderLayout.SOUTH); @@ -801,13 +821,13 @@ public abstract class GStructureChooser extends JPanel * @author tcnofoegbu * */ - public class AssciateSeqPanel extends JPanel implements ItemListener + public class AssociateSeqPanel extends JPanel implements ItemListener { - private JComboBox cmb_assSeq = new JComboBox(); + private JComboBox cmb_assSeq = new JComboBox<>(); private JLabel lbl_associateSeq = new JLabel(); - public AssciateSeqPanel() + public AssociateSeqPanel() { this.setLayout(new FlowLayout()); this.add(cmb_assSeq); @@ -901,19 +921,21 @@ public abstract class GStructureChooser extends JPanel protected abstract void stateChanged(ItemEvent e); - protected abstract void ok_ActionPerformed(); + protected abstract void add_ActionPerformed(); + + protected abstract void newView_ActionPerformed(); protected abstract void pdbFromFile_actionPerformed(); protected abstract void txt_search_ActionPerformed(); - public abstract void populateCmbAssociateSeqOptions( + protected abstract void populateCmbAssociateSeqOptions( JComboBox cmb_assSeq, JLabel lbl_associateSeq); - public abstract void cmbAssSeqStateChanged(); + protected abstract void cmbAssSeqStateChanged(); - public abstract void tabRefresh(); + protected abstract void tabRefresh(); - public abstract void validateSelections(); + protected abstract void validateSelections(); } \ No newline at end of file diff --git a/src/jalview/renderer/AnnotationRenderer.java b/src/jalview/renderer/AnnotationRenderer.java index 8a80d41..adca17e 100644 --- a/src/jalview/renderer/AnnotationRenderer.java +++ b/src/jalview/renderer/AnnotationRenderer.java @@ -163,7 +163,7 @@ public class AnnotationRenderer { g.setColor(STEM_COLOUR); int sCol = (lastSSX / charWidth) - + hiddenColumns.adjustForHiddenColumns(startRes); + + hiddenColumns.visibleToAbsoluteColumn(startRes); int x1 = lastSSX; int x2 = (x * charWidth); @@ -230,7 +230,7 @@ public class AnnotationRenderer g.setColor(nonCanColor); int sCol = (lastSSX / charWidth) - + hiddenColumns.adjustForHiddenColumns(startRes); + + hiddenColumns.visibleToAbsoluteColumn(startRes); int x1 = lastSSX; int x2 = (x * charWidth); @@ -602,7 +602,7 @@ public class AnnotationRenderer { if (hasHiddenColumns) { - column = hiddenColumns.adjustForHiddenColumns(startRes + x); + column = hiddenColumns.visibleToAbsoluteColumn(startRes + x); if (column > row_annotations.length - 1) { break; @@ -1150,7 +1150,7 @@ public class AnnotationRenderer g.setColor(HELIX_COLOUR); int sCol = (lastSSX / charWidth) - + hiddenColumns.adjustForHiddenColumns(startRes); + + hiddenColumns.visibleToAbsoluteColumn(startRes); int x1 = lastSSX; int x2 = (x * charWidth); @@ -1250,7 +1250,7 @@ public class AnnotationRenderer column = sRes + x; if (hasHiddenColumns) { - column = hiddenColumns.adjustForHiddenColumns(column); + column = hiddenColumns.visibleToAbsoluteColumn(column); } if (column > aaMax) @@ -1330,7 +1330,7 @@ public class AnnotationRenderer column = sRes + x; if (hasHiddenColumns) { - column = hiddenColumns.adjustForHiddenColumns(column); + column = hiddenColumns.visibleToAbsoluteColumn(column); } if (column > aaMax) diff --git a/src/jalview/renderer/OverviewRenderer.java b/src/jalview/renderer/OverviewRenderer.java index 1c50aab..e9b4de4 100644 --- a/src/jalview/renderer/OverviewRenderer.java +++ b/src/jalview/renderer/OverviewRenderer.java @@ -44,8 +44,6 @@ public class OverviewRenderer // transparency of hidden cols/seqs overlay private final float TRANSPARENCY = 0.5f; - private final Color HIDDEN_COLOUR = Color.DARK_GRAY.darker(); - public static final String UPDATE = "OverviewUpdate"; private static final int MAX_PROGRESS = 100; @@ -152,7 +150,7 @@ public class OverviewRenderer if (pixelCol <= endCol) { rgbcolor = getColumnColourFromSequence(allGroups, seq, - alignmentCol, finder); + alignmentCol); // fill in the appropriate number of pixels for (int row = pixelRow; row <= endRow; ++row) @@ -216,27 +214,23 @@ public class OverviewRenderer } /* - * Find the colour of a sequence at a specified column position + * Find the RGB value of the colour of a sequence at a specified column position * * @param seq * sequence to get colour for * @param lastcol * column position to get colour for - * @param fcfinder - * FeatureColourFinder to use * @return colour of sequence at this position, as RGB */ - private int getColumnColourFromSequence(SequenceGroup[] allGroups, - jalview.datamodel.SequenceI seq, - int lastcol, FeatureColourFinder fcfinder) + int getColumnColourFromSequence(SequenceGroup[] allGroups, + SequenceI seq, int lastcol) { - Color color = Color.white; + Color color = resColFinder.GAP_COLOUR; if ((seq != null) && (seq.getLength() > lastcol)) { color = resColFinder.getResidueColour(true, shader, allGroups, seq, - lastcol, - fcfinder); + lastcol, finder); } return color.getRGB(); @@ -359,15 +353,13 @@ public class OverviewRenderer * the graphics object to draw on * @param anno * alignment annotation information - * @param charWidth - * alignment character width value * @param y * y-position for the annotation graph * @param cols * the collection of columns used in the overview panel */ - public void drawGraph(Graphics g, AlignmentAnnotation anno, int charWidth, - int y, AlignmentColsCollectionI cols) + public void drawGraph(Graphics g, AlignmentAnnotation anno, int y, + AlignmentColsCollectionI cols) { Annotation[] annotations = anno.annotations; g.setColor(Color.white); diff --git a/src/jalview/renderer/ScaleRenderer.java b/src/jalview/renderer/ScaleRenderer.java index d92608c..dc3272f 100644 --- a/src/jalview/renderer/ScaleRenderer.java +++ b/src/jalview/renderer/ScaleRenderer.java @@ -21,9 +21,11 @@ package jalview.renderer; import jalview.api.AlignViewportI; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceI; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -60,27 +62,19 @@ public class ScaleRenderer column = col; text = txt; } - - /** - * String representation for inspection when debugging only - */ - @Override - public String toString() - { - return String.format("%s:%d:%s", major ? "major" : "minor", column, - text); - } } /** - * Calculates position markers on the alignment ruler + * calculate positions markers on the alignment ruler * * @param av * @param startx - * left-most column in visible view (0..) + * left-most column in visible view * @param endx - * - right-most column in visible view (0..) - * @return + * - right-most column in visible view + * @return List of ScaleMark holding boolean: true/false for major/minor mark, + * marker position in alignment column coords, a String to be rendered + * at the position (or null) */ public List calculateMarks(AlignViewportI av, int startx, int endx) @@ -88,17 +82,27 @@ public class ScaleRenderer int scalestartx = (startx / 10) * 10; SequenceI refSeq = av.getAlignment().getSeqrep(); - int refSp = 0, refStartI = 0, refEndI = -1; + int refSp = 0; + int refStartI = 0; + int refEndI = -1; + + HiddenColumns hc = av.getAlignment().getHiddenColumns(); + if (refSeq != null) { - // find bounds and set origin appopriately - // locate first visible position for this sequence - int[] refbounds = av.getAlignment().getHiddenColumns() - .locateVisibleBoundsOfSequence(refSeq); + // find bounds and set origin appropriately + // locate first residue in sequence which is not hidden + Iterator it = hc.iterator(); + int index = refSeq.firstResidueOutsideIterator(it); + refSp = hc.absoluteToVisibleColumn(index); + + refStartI = refSeq.findIndex(refSeq.getStart()) - 1; + + int seqlength = refSeq.getLength(); + // get sequence position past the end of the sequence + int pastEndPos = refSeq.findPosition(seqlength + 1); + refEndI = refSeq.findIndex(pastEndPos - 1) - 1; - refSp = refbounds[0]; - refStartI = refbounds[4]; - refEndI = refbounds[5]; scalestartx = refSp + ((scalestartx - refSp) / 10) * 10; } @@ -106,41 +110,40 @@ public class ScaleRenderer { scalestartx += 5; } - List marks = new ArrayList(); + List marks = new ArrayList<>(); + String string; + int refN, iadj; // todo: add a 'reference origin column' to set column number relative to for (int i = scalestartx; i <= endx; i += 5) { if (((i - refSp) % 10) == 0) { - String text; if (refSeq == null) { - int iadj = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(i - 1) + 1; - text = String.valueOf(iadj); + iadj = hc.visibleToAbsoluteColumn(i - 1) + 1; + string = String.valueOf(iadj); } else { - int iadj = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(i - 1); - int refN = refSeq.findPosition(iadj); + iadj = hc.visibleToAbsoluteColumn(i - 1); + refN = refSeq.findPosition(iadj); // TODO show bounds if position is a gap // - ie L--R -> "1L|2R" for // marker if (iadj < refStartI) { - text = String.valueOf(iadj - refStartI); + string = String.valueOf(iadj - refStartI); } else if (iadj > refEndI) { - text = "+" + String.valueOf(iadj - refEndI); + string = "+" + String.valueOf(iadj - refEndI); } else { - text = String.valueOf(refN) + refSeq.getCharAt(iadj); + string = String.valueOf(refN) + refSeq.getCharAt(iadj); } } - marks.add(new ScaleMark(true, i - startx - 1, text)); + marks.add(new ScaleMark(true, i - startx - 1, string)); } else { diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index 1f47da3..795cd36 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -304,14 +304,19 @@ public class FeatureRenderer extends FeatureRendererModel List overlaps = seq.getFeatures().findFeatures( visiblePositions.getBegin(), visiblePositions.getEnd(), type); - filterFeaturesForDisplay(overlaps, fc); + if (fc.isSimpleColour()) + { + filterFeaturesForDisplay(overlaps); + } for (SequenceFeature sf : overlaps) { - Color featureColour = fc.getColor(sf); + Color featureColour = getColor(sf, fc); if (featureColour == null) { - // score feature outwith threshold for colouring + /* + * feature excluded by visibility settings, filters, or colour threshold + */ continue; } diff --git a/src/jalview/schemabinding/version2/.castor.cdr b/src/jalview/schemabinding/version2/.castor.cdr index 0a01103..e1100a8 100644 --- a/src/jalview/schemabinding/version2/.castor.cdr +++ b/src/jalview/schemabinding/version2/.castor.cdr @@ -1,4 +1,4 @@ -#Mon Jun 20 15:44:52 BST 2016 +#Thu Dec 14 09:10:14 GMT 2017 jalview.schemabinding.version2.ThresholdLine=jalview.schemabinding.version2.descriptors.ThresholdLineDescriptor jalview.schemabinding.version2.SequenceSetProperties=jalview.schemabinding.version2.descriptors.SequenceSetPropertiesDescriptor jalview.schemabinding.version2.StructureState=jalview.schemabinding.version2.descriptors.StructureStateDescriptor @@ -10,7 +10,9 @@ jalview.schemabinding.version2.OtherData=jalview.schemabinding.version2.descript jalview.schemabinding.version2.Setting=jalview.schemabinding.version2.descriptors.SettingDescriptor jalview.schemabinding.version2.AlcodonFrame=jalview.schemabinding.version2.descriptors.AlcodonFrameDescriptor jalview.schemabinding.version2.AnnotationElement=jalview.schemabinding.version2.descriptors.AnnotationElementDescriptor +jalview.schemabinding.version2.FeatureMatcherSet=jalview.schemabinding.version2.descriptors.FeatureMatcherSetDescriptor jalview.schemabinding.version2.SecondaryStructure=jalview.schemabinding.version2.descriptors.SecondaryStructureDescriptor +jalview.schemabinding.version2.MatchCondition=jalview.schemabinding.version2.descriptors.MatchConditionDescriptor jalview.schemabinding.version2.SequenceSet=jalview.schemabinding.version2.descriptors.SequenceSetDescriptor jalview.schemabinding.version2.Viewport=jalview.schemabinding.version2.descriptors.ViewportDescriptor jalview.schemabinding.version2.RnaViewer=jalview.schemabinding.version2.descriptors.RnaViewerDescriptor @@ -20,31 +22,32 @@ jalview.schemabinding.version2.UserColourScheme=jalview.schemabinding.version2.d jalview.schemabinding.version2.DBRef=jalview.schemabinding.version2.descriptors.DBRefDescriptor jalview.schemabinding.version2.AlcodMap=jalview.schemabinding.version2.descriptors.AlcodMapDescriptor jalview.schemabinding.version2.Annotation=jalview.schemabinding.version2.descriptors.AnnotationDescriptor -jalview.schemabinding.version2.Wsparameters=jalview.schemabinding.version2.descriptors.WsparametersDescriptor jalview.schemabinding.version2.JSeq=jalview.schemabinding.version2.descriptors.JSeqDescriptor +jalview.schemabinding.version2.MatcherSet=jalview.schemabinding.version2.descriptors.MatcherSetDescriptor jalview.schemabinding.version2.Sequence=jalview.schemabinding.version2.descriptors.SequenceDescriptor jalview.schemabinding.version2.WebServiceParameterSet=jalview.schemabinding.version2.descriptors.WebServiceParameterSetDescriptor jalview.schemabinding.version2.Alcodon=jalview.schemabinding.version2.descriptors.AlcodonDescriptor +jalview.schemabinding.version2.Filter=jalview.schemabinding.version2.descriptors.FilterDescriptor jalview.schemabinding.version2.AnnotationColours=jalview.schemabinding.version2.descriptors.AnnotationColoursDescriptor jalview.schemabinding.version2.Pdbids=jalview.schemabinding.version2.descriptors.PdbidsDescriptor jalview.schemabinding.version2.AnnotationColourScheme=jalview.schemabinding.version2.descriptors.AnnotationColourSchemeDescriptor jalview.schemabinding.version2.Mapping=jalview.schemabinding.version2.descriptors.MappingDescriptor -jalview.schemabinding.version2.MappingChoice=jalview.schemabinding.version2.descriptors.MappingChoiceDescriptor +jalview.schemabinding.version2.CompoundMatcher=jalview.schemabinding.version2.descriptors.CompoundMatcherDescriptor +jalview.schemabinding.version2.JalviewModelSequence=jalview.schemabinding.version2.descriptors.JalviewModelSequenceDescriptor jalview.schemabinding.version2.Group=jalview.schemabinding.version2.descriptors.GroupDescriptor +jalview.schemabinding.version2.MappingChoice=jalview.schemabinding.version2.descriptors.MappingChoiceDescriptor jalview.schemabinding.version2.Feature=jalview.schemabinding.version2.descriptors.FeatureDescriptor -jalview.schemabinding.version2.JalviewModelSequence=jalview.schemabinding.version2.descriptors.JalviewModelSequenceDescriptor jalview.schemabinding.version2.UserColours=jalview.schemabinding.version2.descriptors.UserColoursDescriptor jalview.schemabinding.version2.Colour=jalview.schemabinding.version2.descriptors.ColourDescriptor -jalview.schemabinding.version2.MapListFrom=jalview.schemabinding.version2.descriptors.MapListFromDescriptor jalview.schemabinding.version2.PdbentryItem=jalview.schemabinding.version2.descriptors.PdbentryItemDescriptor -jalview.schemabinding.version2.JGroup=jalview.schemabinding.version2.descriptors.JGroupDescriptor +jalview.schemabinding.version2.MapListFrom=jalview.schemabinding.version2.descriptors.MapListFromDescriptor jalview.schemabinding.version2.FeatureSettings=jalview.schemabinding.version2.descriptors.FeatureSettingsDescriptor -jalview.schemabinding.version2.VamsasModel=jalview.schemabinding.version2.descriptors.VamsasModelDescriptor -jalview.schemabinding.version2.JalviewUserColours=jalview.schemabinding.version2.descriptors.JalviewUserColoursDescriptor +jalview.schemabinding.version2.JGroup=jalview.schemabinding.version2.descriptors.JGroupDescriptor jalview.schemabinding.version2.MapListTo=jalview.schemabinding.version2.descriptors.MapListToDescriptor +jalview.schemabinding.version2.JalviewUserColours=jalview.schemabinding.version2.descriptors.JalviewUserColoursDescriptor +jalview.schemabinding.version2.VamsasModel=jalview.schemabinding.version2.descriptors.VamsasModelDescriptor jalview.schemabinding.version2.Pdbentry=jalview.schemabinding.version2.descriptors.PdbentryDescriptor jalview.schemabinding.version2.HiddenColumns=jalview.schemabinding.version2.descriptors.HiddenColumnsDescriptor jalview.schemabinding.version2.Features=jalview.schemabinding.version2.descriptors.FeaturesDescriptor -jalview.schemabinding.version2.DseqFor=jalview.schemabinding.version2.descriptors.DseqForDescriptor jalview.schemabinding.version2.VAMSAS=jalview.schemabinding.version2.descriptors.VAMSASDescriptor -jalview.schemabinding.version2.MappingChoiceItem=jalview.schemabinding.version2.descriptors.MappingChoiceItemDescriptor +jalview.schemabinding.version2.FeatureMatcher=jalview.schemabinding.version2.descriptors.FeatureMatcherDescriptor diff --git a/src/jalview/schemabinding/version2/Colour.java b/src/jalview/schemabinding/version2/Colour.java index 9d5a916..d1c7297 100644 --- a/src/jalview/schemabinding/version2/Colour.java +++ b/src/jalview/schemabinding/version2/Colour.java @@ -27,7 +27,8 @@ public class Colour implements java.io.Serializable // --------------------------/ /** - * Field _name. + * Single letter residue code for an alignment colour scheme, or feature type + * for a feature colour scheme */ private java.lang.String _name; @@ -42,9 +43,15 @@ public class Colour implements java.io.Serializable private java.lang.String _minRGB; /** - * loosely specified enumeration: NONE,ABOVE, or BELOW + * Field _noValueColour. */ - private java.lang.String _threshType; + private jalview.schemabinding.version2.types.NoValueColour _noValueColour = jalview.schemabinding.version2.types.NoValueColour + .valueOf("Min"); + + /** + * Field _threshType. + */ + private jalview.schemabinding.version2.types.ColourThreshTypeType _threshType; /** * Field _threshold. @@ -96,6 +103,11 @@ public class Colour implements java.io.Serializable */ private boolean _has_autoScale; + /** + * name of feature attribute to colour by, or attribute and sub-attribute + */ + private java.util.Vector _attributeNameList; + // ----------------/ // - Constructors -/ // ----------------/ @@ -103,6 +115,9 @@ public class Colour implements java.io.Serializable public Colour() { super(); + setNoValueColour(jalview.schemabinding.version2.types.NoValueColour + .valueOf("Min")); + this._attributeNameList = new java.util.Vector(); } // -----------/ @@ -110,41 +125,140 @@ public class Colour implements java.io.Serializable // -----------/ /** - */ + * + * + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.addElement(vAttributeName); + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.add(index, vAttributeName); + } + + /** + */ public void deleteAutoScale() { this._has_autoScale = false; } /** - */ + */ public void deleteColourByLabel() { this._has_colourByLabel = false; } /** - */ + */ public void deleteMax() { this._has_max = false; } /** - */ + */ public void deleteMin() { this._has_min = false; } /** - */ + */ public void deleteThreshold() { this._has_threshold = false; } /** + * Method enumerateAttributeName. + * + * @return an Enumeration over all java.lang.String elements + */ + public java.util.Enumeration enumerateAttributeName() + { + return this._attributeNameList.elements(); + } + + /** + * Method getAttributeName. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the java.lang.String at the given index + */ + public java.lang.String getAttributeName(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("getAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + return (java.lang.String) _attributeNameList.get(index); + } + + /** + * Method getAttributeName.Returns the contents of the collection in an Array. + *

            + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public java.lang.String[] getAttributeName() + { + java.lang.String[] array = new java.lang.String[0]; + return (java.lang.String[]) this._attributeNameList.toArray(array); + } + + /** + * Method getAttributeNameCount. + * + * @return the size of this collection + */ + public int getAttributeNameCount() + { + return this._attributeNameList.size(); + } + + /** * Returns the value of field 'autoScale'. * * @return the value of field 'AutoScale'. @@ -195,7 +309,9 @@ public class Colour implements java.io.Serializable } /** - * Returns the value of field 'name'. + * Returns the value of field 'name'. The field 'name' has the following + * description: Single letter residue code for an alignment colour scheme, or + * feature type for a feature colour scheme * * @return the value of field 'Name'. */ @@ -205,6 +321,16 @@ public class Colour implements java.io.Serializable } /** + * Returns the value of field 'noValueColour'. + * + * @return the value of field 'NoValueColour'. + */ + public jalview.schemabinding.version2.types.NoValueColour getNoValueColour() + { + return this._noValueColour; + } + + /** * Returns the value of field 'RGB'. * * @return the value of field 'RGB'. @@ -215,12 +341,11 @@ public class Colour implements java.io.Serializable } /** - * Returns the value of field 'threshType'. The field 'threshType' has the - * following description: loosely specified enumeration: NONE,ABOVE, or BELOW + * Returns the value of field 'threshType'. * * @return the value of field 'ThreshType'. */ - public java.lang.String getThreshType() + public jalview.schemabinding.version2.types.ColourThreshTypeType getThreshType() { return this._threshType; } @@ -360,6 +485,76 @@ public class Colour implements java.io.Serializable } /** + */ + public void removeAllAttributeName() + { + this._attributeNameList.clear(); + } + + /** + * Method removeAttributeName. + * + * @param vAttributeName + * @return true if the object was removed from the collection. + */ + public boolean removeAttributeName(final java.lang.String vAttributeName) + { + boolean removed = _attributeNameList.remove(vAttributeName); + return removed; + } + + /** + * Method removeAttributeNameAt. + * + * @param index + * @return the element removed from the collection + */ + public java.lang.String removeAttributeNameAt(final int index) + { + java.lang.Object obj = this._attributeNameList.remove(index); + return (java.lang.String) obj; + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("setAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + this._attributeNameList.set(index, vAttributeName); + } + + /** + * + * + * @param vAttributeNameArray + */ + public void setAttributeName(final java.lang.String[] vAttributeNameArray) + { + // -- copy array + _attributeNameList.clear(); + + for (int i = 0; i < vAttributeNameArray.length; i++) + { + this._attributeNameList.add(vAttributeNameArray[i]); + } + } + + /** * Sets the value of field 'autoScale'. * * @param autoScale @@ -419,7 +614,9 @@ public class Colour implements java.io.Serializable } /** - * Sets the value of field 'name'. + * Sets the value of field 'name'. The field 'name' has the following + * description: Single letter residue code for an alignment colour scheme, or + * feature type for a feature colour scheme * * @param name * the value of field 'name'. @@ -430,6 +627,18 @@ public class Colour implements java.io.Serializable } /** + * Sets the value of field 'noValueColour'. + * + * @param noValueColour + * the value of field 'noValueColour'. + */ + public void setNoValueColour( + final jalview.schemabinding.version2.types.NoValueColour noValueColour) + { + this._noValueColour = noValueColour; + } + + /** * Sets the value of field 'RGB'. * * @param RGB @@ -441,13 +650,13 @@ public class Colour implements java.io.Serializable } /** - * Sets the value of field 'threshType'. The field 'threshType' has the - * following description: loosely specified enumeration: NONE,ABOVE, or BELOW + * Sets the value of field 'threshType'. * * @param threshType * the value of field 'threshType'. */ - public void setThreshType(final java.lang.String threshType) + public void setThreshType( + final jalview.schemabinding.version2.types.ColourThreshTypeType threshType) { this._threshType = threshType; } @@ -480,8 +689,8 @@ public class Colour implements java.io.Serializable throws org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { - return (jalview.schemabinding.version2.Colour) Unmarshaller.unmarshal( - jalview.schemabinding.version2.Colour.class, reader); + return (jalview.schemabinding.version2.Colour) Unmarshaller + .unmarshal(jalview.schemabinding.version2.Colour.class, reader); } /** diff --git a/src/jalview/schemabinding/version2/CompoundMatcher.java b/src/jalview/schemabinding/version2/CompoundMatcher.java new file mode 100644 index 0000000..27714e2 --- /dev/null +++ b/src/jalview/schemabinding/version2/CompoundMatcher.java @@ -0,0 +1,374 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class CompoundMatcher. + * + * @version $Revision$ $Date$ + */ +public class CompoundMatcher implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * If true, matchers are AND-ed, if false they are OR-ed + */ + private boolean _and; + + /** + * keeps track of state for field: _and + */ + private boolean _has_and; + + /** + * Field _matcherSetList. + */ + private java.util.Vector _matcherSetList; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public CompoundMatcher() + { + super(); + this._matcherSetList = new java.util.Vector(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * + * + * @param vMatcherSet + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addMatcherSet( + final jalview.schemabinding.version2.MatcherSet vMatcherSet) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._matcherSetList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addMatcherSet has a maximum of 2"); + } + + this._matcherSetList.addElement(vMatcherSet); + } + + /** + * + * + * @param index + * @param vMatcherSet + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addMatcherSet(final int index, + final jalview.schemabinding.version2.MatcherSet vMatcherSet) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._matcherSetList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addMatcherSet has a maximum of 2"); + } + + this._matcherSetList.add(index, vMatcherSet); + } + + /** + */ + public void deleteAnd() + { + this._has_and = false; + } + + /** + * Method enumerateMatcherSet. + * + * @return an Enumeration over all jalview.schemabinding.version2.MatcherSet + * elements + */ + public java.util.Enumeration enumerateMatcherSet() + { + return this._matcherSetList.elements(); + } + + /** + * Returns the value of field 'and'. The field 'and' has the following + * description: If true, matchers are AND-ed, if false they are OR-ed + * + * @return the value of field 'And'. + */ + public boolean getAnd() + { + return this._and; + } + + /** + * Method getMatcherSet. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the jalview.schemabinding.version2.MatcherSet at the + * given index + */ + public jalview.schemabinding.version2.MatcherSet getMatcherSet( + final int index) throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._matcherSetList.size()) + { + throw new IndexOutOfBoundsException( + "getMatcherSet: Index value '" + index + "' not in range [0.." + + (this._matcherSetList.size() - 1) + "]"); + } + + return (jalview.schemabinding.version2.MatcherSet) _matcherSetList + .get(index); + } + + /** + * Method getMatcherSet.Returns the contents of the collection in an Array. + *

            + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public jalview.schemabinding.version2.MatcherSet[] getMatcherSet() + { + jalview.schemabinding.version2.MatcherSet[] array = new jalview.schemabinding.version2.MatcherSet[0]; + return (jalview.schemabinding.version2.MatcherSet[]) this._matcherSetList + .toArray(array); + } + + /** + * Method getMatcherSetCount. + * + * @return the size of this collection + */ + public int getMatcherSetCount() + { + return this._matcherSetList.size(); + } + + /** + * Method hasAnd. + * + * @return true if at least one And has been added + */ + public boolean hasAnd() + { + return this._has_and; + } + + /** + * Returns the value of field 'and'. The field 'and' has the following + * description: If true, matchers are AND-ed, if false they are OR-ed + * + * @return the value of field 'And'. + */ + public boolean isAnd() + { + return this._and; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + */ + public void removeAllMatcherSet() + { + this._matcherSetList.clear(); + } + + /** + * Method removeMatcherSet. + * + * @param vMatcherSet + * @return true if the object was removed from the collection. + */ + public boolean removeMatcherSet( + final jalview.schemabinding.version2.MatcherSet vMatcherSet) + { + boolean removed = _matcherSetList.remove(vMatcherSet); + return removed; + } + + /** + * Method removeMatcherSetAt. + * + * @param index + * @return the element removed from the collection + */ + public jalview.schemabinding.version2.MatcherSet removeMatcherSetAt( + final int index) + { + java.lang.Object obj = this._matcherSetList.remove(index); + return (jalview.schemabinding.version2.MatcherSet) obj; + } + + /** + * Sets the value of field 'and'. The field 'and' has the following + * description: If true, matchers are AND-ed, if false they are OR-ed + * + * @param and + * the value of field 'and'. + */ + public void setAnd(final boolean and) + { + this._and = and; + this._has_and = true; + } + + /** + * + * + * @param index + * @param vMatcherSet + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setMatcherSet(final int index, + final jalview.schemabinding.version2.MatcherSet vMatcherSet) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._matcherSetList.size()) + { + throw new IndexOutOfBoundsException( + "setMatcherSet: Index value '" + index + "' not in range [0.." + + (this._matcherSetList.size() - 1) + "]"); + } + + this._matcherSetList.set(index, vMatcherSet); + } + + /** + * + * + * @param vMatcherSetArray + */ + public void setMatcherSet( + final jalview.schemabinding.version2.MatcherSet[] vMatcherSetArray) + { + // -- copy array + _matcherSetList.clear(); + + for (int i = 0; i < vMatcherSetArray.length; i++) + { + this._matcherSetList.add(vMatcherSetArray[i]); + } + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.schemabinding.version2.CompoundMatcher + */ + public static jalview.schemabinding.version2.CompoundMatcher unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.schemabinding.version2.CompoundMatcher) Unmarshaller + .unmarshal(jalview.schemabinding.version2.CompoundMatcher.class, + reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/schemabinding/version2/FeatureMatcher.java b/src/jalview/schemabinding/version2/FeatureMatcher.java new file mode 100644 index 0000000..4d29cab --- /dev/null +++ b/src/jalview/schemabinding/version2/FeatureMatcher.java @@ -0,0 +1,383 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class FeatureMatcher. + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcher implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _by. + */ + private jalview.schemabinding.version2.types.FeatureMatcherByType _by; + + /** + * name of feature attribute to filter on, or attribute and sub-attribute + */ + private java.util.Vector _attributeNameList; + + /** + * Field _condition. + */ + private java.lang.String _condition; + + /** + * Field _value. + */ + private java.lang.String _value; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public FeatureMatcher() + { + super(); + this._attributeNameList = new java.util.Vector(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * + * + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.addElement(vAttributeName); + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.add(index, vAttributeName); + } + + /** + * Method enumerateAttributeName. + * + * @return an Enumeration over all java.lang.String elements + */ + public java.util.Enumeration enumerateAttributeName() + { + return this._attributeNameList.elements(); + } + + /** + * Method getAttributeName. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the java.lang.String at the given index + */ + public java.lang.String getAttributeName(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("getAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + return (java.lang.String) _attributeNameList.get(index); + } + + /** + * Method getAttributeName.Returns the contents of the collection in an Array. + *

            + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public java.lang.String[] getAttributeName() + { + java.lang.String[] array = new java.lang.String[0]; + return (java.lang.String[]) this._attributeNameList.toArray(array); + } + + /** + * Method getAttributeNameCount. + * + * @return the size of this collection + */ + public int getAttributeNameCount() + { + return this._attributeNameList.size(); + } + + /** + * Returns the value of field 'by'. + * + * @return the value of field 'By'. + */ + public jalview.schemabinding.version2.types.FeatureMatcherByType getBy() + { + return this._by; + } + + /** + * Returns the value of field 'condition'. + * + * @return the value of field 'Condition'. + */ + public java.lang.String getCondition() + { + return this._condition; + } + + /** + * Returns the value of field 'value'. + * + * @return the value of field 'Value'. + */ + public java.lang.String getValue() + { + return this._value; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + */ + public void removeAllAttributeName() + { + this._attributeNameList.clear(); + } + + /** + * Method removeAttributeName. + * + * @param vAttributeName + * @return true if the object was removed from the collection. + */ + public boolean removeAttributeName(final java.lang.String vAttributeName) + { + boolean removed = _attributeNameList.remove(vAttributeName); + return removed; + } + + /** + * Method removeAttributeNameAt. + * + * @param index + * @return the element removed from the collection + */ + public java.lang.String removeAttributeNameAt(final int index) + { + java.lang.Object obj = this._attributeNameList.remove(index); + return (java.lang.String) obj; + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("setAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + this._attributeNameList.set(index, vAttributeName); + } + + /** + * + * + * @param vAttributeNameArray + */ + public void setAttributeName(final java.lang.String[] vAttributeNameArray) + { + // -- copy array + _attributeNameList.clear(); + + for (int i = 0; i < vAttributeNameArray.length; i++) + { + this._attributeNameList.add(vAttributeNameArray[i]); + } + } + + /** + * Sets the value of field 'by'. + * + * @param by + * the value of field 'by'. + */ + public void setBy( + final jalview.schemabinding.version2.types.FeatureMatcherByType by) + { + this._by = by; + } + + /** + * Sets the value of field 'condition'. + * + * @param condition + * the value of field 'condition'. + */ + public void setCondition(final java.lang.String condition) + { + this._condition = condition; + } + + /** + * Sets the value of field 'value'. + * + * @param value + * the value of field 'value'. + */ + public void setValue(final java.lang.String value) + { + this._value = value; + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.schemabinding.version2.FeatureMatcher + */ + public static jalview.schemabinding.version2.FeatureMatcher unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.schemabinding.version2.FeatureMatcher) Unmarshaller + .unmarshal(jalview.schemabinding.version2.FeatureMatcher.class, + reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/schemabinding/version2/FeatureMatcherSet.java b/src/jalview/schemabinding/version2/FeatureMatcherSet.java new file mode 100644 index 0000000..2d79a98 --- /dev/null +++ b/src/jalview/schemabinding/version2/FeatureMatcherSet.java @@ -0,0 +1,200 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * A feature match condition, which may be simple or compound + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcherSet implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Internal choice value storage + */ + private java.lang.Object _choiceValue; + + /** + * Field _matchCondition. + */ + private MatchCondition _matchCondition; + + /** + * Field _compoundMatcher. + */ + private CompoundMatcher _compoundMatcher; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public FeatureMatcherSet() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Returns the value of field 'choiceValue'. The field 'choiceValue' has the + * following description: Internal choice value storage + * + * @return the value of field 'ChoiceValue'. + */ + public java.lang.Object getChoiceValue() + { + return this._choiceValue; + } + + /** + * Returns the value of field 'compoundMatcher'. + * + * @return the value of field 'CompoundMatcher'. + */ + public CompoundMatcher getCompoundMatcher() + { + return this._compoundMatcher; + } + + /** + * Returns the value of field 'matchCondition'. + * + * @return the value of field 'MatchCondition'. + */ + public MatchCondition getMatchCondition() + { + return this._matchCondition; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Sets the value of field 'compoundMatcher'. + * + * @param compoundMatcher + * the value of field 'compoundMatcher'. + */ + public void setCompoundMatcher(final CompoundMatcher compoundMatcher) + { + this._compoundMatcher = compoundMatcher; + this._choiceValue = compoundMatcher; + } + + /** + * Sets the value of field 'matchCondition'. + * + * @param matchCondition + * the value of field 'matchCondition'. + */ + public void setMatchCondition(final MatchCondition matchCondition) + { + this._matchCondition = matchCondition; + this._choiceValue = matchCondition; + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.schemabinding.version2.FeatureMatcherSet + */ + public static jalview.schemabinding.version2.FeatureMatcherSet unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.schemabinding.version2.FeatureMatcherSet) Unmarshaller + .unmarshal( + jalview.schemabinding.version2.FeatureMatcherSet.class, + reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/schemabinding/version2/Filter.java b/src/jalview/schemabinding/version2/Filter.java new file mode 100644 index 0000000..45323a7 --- /dev/null +++ b/src/jalview/schemabinding/version2/Filter.java @@ -0,0 +1,181 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class Filter. + * + * @version $Revision$ $Date$ + */ +public class Filter implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _featureType. + */ + private java.lang.String _featureType; + + /** + * Field _matcherSet. + */ + private jalview.schemabinding.version2.MatcherSet _matcherSet; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public Filter() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Returns the value of field 'featureType'. + * + * @return the value of field 'FeatureType'. + */ + public java.lang.String getFeatureType() + { + return this._featureType; + } + + /** + * Returns the value of field 'matcherSet'. + * + * @return the value of field 'MatcherSet'. + */ + public jalview.schemabinding.version2.MatcherSet getMatcherSet() + { + return this._matcherSet; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Sets the value of field 'featureType'. + * + * @param featureType + * the value of field 'featureType'. + */ + public void setFeatureType(final java.lang.String featureType) + { + this._featureType = featureType; + } + + /** + * Sets the value of field 'matcherSet'. + * + * @param matcherSet + * the value of field 'matcherSet'. + */ + public void setMatcherSet( + final jalview.schemabinding.version2.MatcherSet matcherSet) + { + this._matcherSet = matcherSet; + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.schemabinding.version2.Filter + */ + public static jalview.schemabinding.version2.Filter unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.schemabinding.version2.Filter) Unmarshaller + .unmarshal(jalview.schemabinding.version2.Filter.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/schemabinding/version2/JalviewUserColours.java b/src/jalview/schemabinding/version2/JalviewUserColours.java index 042f092..c8d52ac 100644 --- a/src/jalview/schemabinding/version2/JalviewUserColours.java +++ b/src/jalview/schemabinding/version2/JalviewUserColours.java @@ -42,6 +42,11 @@ public class JalviewUserColours implements java.io.Serializable */ private java.util.Vector _colourList; + /** + * Field _filterList. + */ + private java.util.Vector _filterList; + // ----------------/ // - Constructors -/ // ----------------/ @@ -50,6 +55,7 @@ public class JalviewUserColours implements java.io.Serializable { super(); this._colourList = new java.util.Vector(); + this._filterList = new java.util.Vector(); } // -----------/ @@ -84,6 +90,33 @@ public class JalviewUserColours implements java.io.Serializable } /** + * + * + * @param vFilter + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addFilter(final Filter vFilter) + throws java.lang.IndexOutOfBoundsException + { + this._filterList.addElement(vFilter); + } + + /** + * + * + * @param index + * @param vFilter + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addFilter(final int index, final Filter vFilter) + throws java.lang.IndexOutOfBoundsException + { + this._filterList.add(index, vFilter); + } + + /** * Method enumerateColour. * * @return an Enumeration over all Colour elements @@ -94,6 +127,16 @@ public class JalviewUserColours implements java.io.Serializable } /** + * Method enumerateFilter. + * + * @return an Enumeration over all Filter elements + */ + public java.util.Enumeration enumerateFilter() + { + return this._filterList.elements(); + } + + /** * Method getColour. * * @param index @@ -107,9 +150,9 @@ public class JalviewUserColours implements java.io.Serializable // check bounds for index if (index < 0 || index >= this._colourList.size()) { - throw new IndexOutOfBoundsException("getColour: Index value '" - + index + "' not in range [0.." - + (this._colourList.size() - 1) + "]"); + throw new IndexOutOfBoundsException( + "getColour: Index value '" + index + "' not in range [0.." + + (this._colourList.size() - 1) + "]"); } return (Colour) _colourList.get(index); @@ -141,6 +184,53 @@ public class JalviewUserColours implements java.io.Serializable } /** + * Method getFilter. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the Filter at the given index + */ + public Filter getFilter(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._filterList.size()) + { + throw new IndexOutOfBoundsException( + "getFilter: Index value '" + index + "' not in range [0.." + + (this._filterList.size() - 1) + "]"); + } + + return (Filter) _filterList.get(index); + } + + /** + * Method getFilter.Returns the contents of the collection in an Array. + *

            + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public Filter[] getFilter() + { + Filter[] array = new Filter[0]; + return (Filter[]) this._filterList.toArray(array); + } + + /** + * Method getFilterCount. + * + * @return the size of this collection + */ + public int getFilterCount() + { + return this._filterList.size(); + } + + /** * Returns the value of field 'schemeName'. * * @return the value of field 'SchemeName'. @@ -217,13 +307,20 @@ public class JalviewUserColours implements java.io.Serializable } /** - */ + */ public void removeAllColour() { this._colourList.clear(); } /** + */ + public void removeAllFilter() + { + this._filterList.clear(); + } + + /** * Method removeColour. * * @param vColour @@ -248,6 +345,30 @@ public class JalviewUserColours implements java.io.Serializable } /** + * Method removeFilter. + * + * @param vFilter + * @return true if the object was removed from the collection. + */ + public boolean removeFilter(final Filter vFilter) + { + boolean removed = _filterList.remove(vFilter); + return removed; + } + + /** + * Method removeFilterAt. + * + * @param index + * @return the element removed from the collection + */ + public Filter removeFilterAt(final int index) + { + java.lang.Object obj = this._filterList.remove(index); + return (Filter) obj; + } + + /** * * * @param index @@ -261,9 +382,9 @@ public class JalviewUserColours implements java.io.Serializable // check bounds for index if (index < 0 || index >= this._colourList.size()) { - throw new IndexOutOfBoundsException("setColour: Index value '" - + index + "' not in range [0.." - + (this._colourList.size() - 1) + "]"); + throw new IndexOutOfBoundsException( + "setColour: Index value '" + index + "' not in range [0.." + + (this._colourList.size() - 1) + "]"); } this._colourList.set(index, vColour); @@ -286,6 +407,44 @@ public class JalviewUserColours implements java.io.Serializable } /** + * + * + * @param index + * @param vFilter + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setFilter(final int index, final Filter vFilter) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._filterList.size()) + { + throw new IndexOutOfBoundsException( + "setFilter: Index value '" + index + "' not in range [0.." + + (this._filterList.size() - 1) + "]"); + } + + this._filterList.set(index, vFilter); + } + + /** + * + * + * @param vFilterArray + */ + public void setFilter(final Filter[] vFilterArray) + { + // -- copy array + _filterList.clear(); + + for (int i = 0; i < vFilterArray.length; i++) + { + this._filterList.add(vFilterArray[i]); + } + } + + /** * Sets the value of field 'schemeName'. * * @param schemeName diff --git a/src/jalview/schemabinding/version2/MatchCondition.java b/src/jalview/schemabinding/version2/MatchCondition.java new file mode 100644 index 0000000..af2f3f5 --- /dev/null +++ b/src/jalview/schemabinding/version2/MatchCondition.java @@ -0,0 +1,126 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * Class MatchCondition. + * + * @version $Revision$ $Date$ + */ +public class MatchCondition extends FeatureMatcher + implements java.io.Serializable +{ + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public MatchCondition() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.schemabinding.version2.FeatureMatcher + */ + public static jalview.schemabinding.version2.FeatureMatcher unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.schemabinding.version2.FeatureMatcher) Unmarshaller + .unmarshal(jalview.schemabinding.version2.MatchCondition.class, + reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/schemabinding/version2/MatcherSet.java b/src/jalview/schemabinding/version2/MatcherSet.java new file mode 100644 index 0000000..6fde9e4 --- /dev/null +++ b/src/jalview/schemabinding/version2/MatcherSet.java @@ -0,0 +1,126 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + +/** + * optional filter(s) applied to the feature type + * + * @version $Revision$ $Date$ + */ +public class MatcherSet extends FeatureMatcherSet + implements java.io.Serializable +{ + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public MatcherSet() + { + super(); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid() + { + try + { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) + { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void marshal(final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException + * if an IOException occurs during marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + */ + public void marshal(final org.xml.sax.ContentHandler handler) + throws java.io.IOException, + org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + Marshaller.marshal(this, handler); + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException + * if object is null or if any SAXException is thrown during + * marshaling + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + * @return the unmarshaled jalview.schemabinding.version2.FeatureMatcherSet + */ + public static jalview.schemabinding.version2.FeatureMatcherSet unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, + org.exolab.castor.xml.ValidationException + { + return (jalview.schemabinding.version2.FeatureMatcherSet) Unmarshaller + .unmarshal(jalview.schemabinding.version2.MatcherSet.class, + reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException + * if this object is an invalid instance according to the schema + */ + public void validate() throws org.exolab.castor.xml.ValidationException + { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); + } + +} diff --git a/src/jalview/schemabinding/version2/OtherData.java b/src/jalview/schemabinding/version2/OtherData.java index fb6b276..31797fe 100644 --- a/src/jalview/schemabinding/version2/OtherData.java +++ b/src/jalview/schemabinding/version2/OtherData.java @@ -7,8 +7,8 @@ package jalview.schemabinding.version2; -//---------------------------------/ -//- Imported classes and packages -/ + //---------------------------------/ + //- Imported classes and packages -/ //---------------------------------/ import org.exolab.castor.xml.Marshaller; @@ -19,163 +19,181 @@ import org.exolab.castor.xml.Unmarshaller; * * @version $Revision$ $Date$ */ -public class OtherData implements java.io.Serializable -{ - - // --------------------------/ - // - Class/Member Variables -/ - // --------------------------/ - - /** - * Field _key. - */ - private java.lang.String _key; - - /** - * Field _value. - */ - private java.lang.String _value; - - // ----------------/ - // - Constructors -/ - // ----------------/ - - public OtherData() - { - super(); - } - - // -----------/ - // - Methods -/ - // -----------/ - - /** - * Returns the value of field 'key'. - * - * @return the value of field 'Key'. - */ - public java.lang.String getKey() - { - return this._key; - } - - /** - * Returns the value of field 'value'. - * - * @return the value of field 'Value'. - */ - public java.lang.String getValue() - { - return this._value; - } - - /** - * Method isValid. - * - * @return true if this object is valid according to the schema - */ - public boolean isValid() - { - try - { - validate(); - } catch (org.exolab.castor.xml.ValidationException vex) - { - return false; +public class OtherData implements java.io.Serializable { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * Field _key. + */ + private java.lang.String _key; + + /** + * key2 may be used for a sub-attribute of key + */ + private java.lang.String _key2; + + /** + * Field _value. + */ + private java.lang.String _value; + + + //----------------/ + //- Constructors -/ + //----------------/ + + public OtherData() { + super(); + } + + + //-----------/ + //- Methods -/ + //-----------/ + + /** + * Returns the value of field 'key'. + * + * @return the value of field 'Key'. + */ + public java.lang.String getKey( + ) { + return this._key; + } + + /** + * Returns the value of field 'key2'. The field 'key2' has the + * following description: key2 may be used for a sub-attribute + * of key + * + * @return the value of field 'Key2'. + */ + public java.lang.String getKey2( + ) { + return this._key2; + } + + /** + * Returns the value of field 'value'. + * + * @return the value of field 'Value'. + */ + public java.lang.String getValue( + ) { + return this._value; + } + + /** + * Method isValid. + * + * @return true if this object is valid according to the schema + */ + public boolean isValid( + ) { + try { + validate(); + } catch (org.exolab.castor.xml.ValidationException vex) { + return false; + } + return true; + } + + /** + * + * + * @param out + * @throws org.exolab.castor.xml.MarshalException if object is + * null or if any SAXException is thrown during marshaling + * @throws org.exolab.castor.xml.ValidationException if this + * object is an invalid instance according to the schema + */ + public void marshal( + final java.io.Writer out) + throws org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + Marshaller.marshal(this, out); + } + + /** + * + * + * @param handler + * @throws java.io.IOException if an IOException occurs during + * marshaling + * @throws org.exolab.castor.xml.ValidationException if this + * object is an invalid instance according to the schema + * @throws org.exolab.castor.xml.MarshalException if object is + * null or if any SAXException is thrown during marshaling + */ + public void marshal( + final org.xml.sax.ContentHandler handler) + throws java.io.IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + Marshaller.marshal(this, handler); + } + + /** + * Sets the value of field 'key'. + * + * @param key the value of field 'key'. + */ + public void setKey( + final java.lang.String key) { + this._key = key; + } + + /** + * Sets the value of field 'key2'. The field 'key2' has the + * following description: key2 may be used for a sub-attribute + * of key + * + * @param key2 the value of field 'key2'. + */ + public void setKey2( + final java.lang.String key2) { + this._key2 = key2; + } + + /** + * Sets the value of field 'value'. + * + * @param value the value of field 'value'. + */ + public void setValue( + final java.lang.String value) { + this._value = value; + } + + /** + * Method unmarshal. + * + * @param reader + * @throws org.exolab.castor.xml.MarshalException if object is + * null or if any SAXException is thrown during marshaling + * @throws org.exolab.castor.xml.ValidationException if this + * object is an invalid instance according to the schema + * @return the unmarshaled + * jalview.schemabinding.version2.OtherData + */ + public static jalview.schemabinding.version2.OtherData unmarshal( + final java.io.Reader reader) + throws org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + return (jalview.schemabinding.version2.OtherData) Unmarshaller.unmarshal(jalview.schemabinding.version2.OtherData.class, reader); + } + + /** + * + * + * @throws org.exolab.castor.xml.ValidationException if this + * object is an invalid instance according to the schema + */ + public void validate( + ) + throws org.exolab.castor.xml.ValidationException { + org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); + validator.validate(this); } - return true; - } - - /** - * - * - * @param out - * @throws org.exolab.castor.xml.MarshalException - * if object is null or if any SAXException is thrown during - * marshaling - * @throws org.exolab.castor.xml.ValidationException - * if this object is an invalid instance according to the schema - */ - public void marshal(final java.io.Writer out) - throws org.exolab.castor.xml.MarshalException, - org.exolab.castor.xml.ValidationException - { - Marshaller.marshal(this, out); - } - - /** - * - * - * @param handler - * @throws java.io.IOException - * if an IOException occurs during marshaling - * @throws org.exolab.castor.xml.ValidationException - * if this object is an invalid instance according to the schema - * @throws org.exolab.castor.xml.MarshalException - * if object is null or if any SAXException is thrown during - * marshaling - */ - public void marshal(final org.xml.sax.ContentHandler handler) - throws java.io.IOException, - org.exolab.castor.xml.MarshalException, - org.exolab.castor.xml.ValidationException - { - Marshaller.marshal(this, handler); - } - - /** - * Sets the value of field 'key'. - * - * @param key - * the value of field 'key'. - */ - public void setKey(final java.lang.String key) - { - this._key = key; - } - - /** - * Sets the value of field 'value'. - * - * @param value - * the value of field 'value'. - */ - public void setValue(final java.lang.String value) - { - this._value = value; - } - - /** - * Method unmarshal. - * - * @param reader - * @throws org.exolab.castor.xml.MarshalException - * if object is null or if any SAXException is thrown during - * marshaling - * @throws org.exolab.castor.xml.ValidationException - * if this object is an invalid instance according to the schema - * @return the unmarshaled jalview.schemabinding.version2.OtherData - */ - public static jalview.schemabinding.version2.OtherData unmarshal( - final java.io.Reader reader) - throws org.exolab.castor.xml.MarshalException, - org.exolab.castor.xml.ValidationException - { - return (jalview.schemabinding.version2.OtherData) Unmarshaller - .unmarshal(jalview.schemabinding.version2.OtherData.class, - reader); - } - - /** - * - * - * @throws org.exolab.castor.xml.ValidationException - * if this object is an invalid instance according to the schema - */ - public void validate() throws org.exolab.castor.xml.ValidationException - { - org.exolab.castor.xml.Validator validator = new org.exolab.castor.xml.Validator(); - validator.validate(this); - } } diff --git a/src/jalview/schemabinding/version2/Setting.java b/src/jalview/schemabinding/version2/Setting.java index c458971..59e9522 100644 --- a/src/jalview/schemabinding/version2/Setting.java +++ b/src/jalview/schemabinding/version2/Setting.java @@ -73,6 +73,12 @@ public class Setting implements java.io.Serializable private boolean _has_mincolour; /** + * Field _noValueColour. + */ + private jalview.schemabinding.version2.types.NoValueColour _noValueColour = jalview.schemabinding.version2.types.NoValueColour + .valueOf("Min"); + + /** * threshold value for graduated feature colour * */ @@ -134,6 +140,16 @@ public class Setting implements java.io.Serializable */ private boolean _has_autoScale; + /** + * name of feature attribute to colour by, or attribute and sub-attribute + */ + private java.util.Vector _attributeNameList; + + /** + * optional filter(s) applied to the feature type + */ + private jalview.schemabinding.version2.MatcherSet _matcherSet; + // ----------------/ // - Constructors -/ // ----------------/ @@ -141,6 +157,9 @@ public class Setting implements java.io.Serializable public Setting() { super(); + setNoValueColour(jalview.schemabinding.version2.types.NoValueColour + .valueOf("Min")); + this._attributeNameList = new java.util.Vector(); } // -----------/ @@ -148,76 +167,175 @@ public class Setting implements java.io.Serializable // -----------/ /** - */ + * + * + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.addElement(vAttributeName); + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void addAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check for the maximum size + if (this._attributeNameList.size() >= 2) + { + throw new IndexOutOfBoundsException( + "addAttributeName has a maximum of 2"); + } + + this._attributeNameList.add(index, vAttributeName); + } + + /** + */ public void deleteAutoScale() { this._has_autoScale = false; } /** - */ + */ public void deleteColour() { this._has_colour = false; } /** - */ + */ public void deleteColourByLabel() { this._has_colourByLabel = false; } /** - */ + */ public void deleteDisplay() { this._has_display = false; } /** - */ + */ public void deleteMax() { this._has_max = false; } /** - */ + */ public void deleteMin() { this._has_min = false; } /** - */ + */ public void deleteMincolour() { this._has_mincolour = false; } /** - */ + */ public void deleteOrder() { this._has_order = false; } /** - */ + */ public void deleteThreshold() { this._has_threshold = false; } /** - */ + */ public void deleteThreshstate() { this._has_threshstate = false; } /** + * Method enumerateAttributeName. + * + * @return an Enumeration over all java.lang.String elements + */ + public java.util.Enumeration enumerateAttributeName() + { + return this._attributeNameList.elements(); + } + + /** + * Method getAttributeName. + * + * @param index + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + * @return the value of the java.lang.String at the given index + */ + public java.lang.String getAttributeName(final int index) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("getAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + return (java.lang.String) _attributeNameList.get(index); + } + + /** + * Method getAttributeName.Returns the contents of the collection in an Array. + *

            + * Note: Just in case the collection contents are changing in another thread, + * we pass a 0-length Array of the correct type into the API call. This way we + * know that the Array returned is of exactly the correct length. + * + * @return this collection as an Array + */ + public java.lang.String[] getAttributeName() + { + java.lang.String[] array = new java.lang.String[0]; + return (java.lang.String[]) this._attributeNameList.toArray(array); + } + + /** + * Method getAttributeNameCount. + * + * @return the size of this collection + */ + public int getAttributeNameCount() + { + return this._attributeNameList.size(); + } + + /** * Returns the value of field 'autoScale'. * * @return the value of field 'AutoScale'. @@ -258,6 +376,17 @@ public class Setting implements java.io.Serializable } /** + * Returns the value of field 'matcherSet'. The field 'matcherSet' has the + * following description: optional filter(s) applied to the feature type + * + * @return the value of field 'MatcherSet'. + */ + public jalview.schemabinding.version2.MatcherSet getMatcherSet() + { + return this._matcherSet; + } + + /** * Returns the value of field 'max'. * * @return the value of field 'Max'. @@ -290,6 +419,16 @@ public class Setting implements java.io.Serializable } /** + * Returns the value of field 'noValueColour'. + * + * @return the value of field 'NoValueColour'. + */ + public jalview.schemabinding.version2.types.NoValueColour getNoValueColour() + { + return this._noValueColour; + } + + /** * Returns the value of field 'order'. * * @return the value of field 'Order'. @@ -518,6 +657,76 @@ public class Setting implements java.io.Serializable } /** + */ + public void removeAllAttributeName() + { + this._attributeNameList.clear(); + } + + /** + * Method removeAttributeName. + * + * @param vAttributeName + * @return true if the object was removed from the collection. + */ + public boolean removeAttributeName(final java.lang.String vAttributeName) + { + boolean removed = _attributeNameList.remove(vAttributeName); + return removed; + } + + /** + * Method removeAttributeNameAt. + * + * @param index + * @return the element removed from the collection + */ + public java.lang.String removeAttributeNameAt(final int index) + { + java.lang.Object obj = this._attributeNameList.remove(index); + return (java.lang.String) obj; + } + + /** + * + * + * @param index + * @param vAttributeName + * @throws java.lang.IndexOutOfBoundsException + * if the index given is outside the bounds of the collection + */ + public void setAttributeName(final int index, + final java.lang.String vAttributeName) + throws java.lang.IndexOutOfBoundsException + { + // check bounds for index + if (index < 0 || index >= this._attributeNameList.size()) + { + throw new IndexOutOfBoundsException("setAttributeName: Index value '" + + index + "' not in range [0.." + + (this._attributeNameList.size() - 1) + "]"); + } + + this._attributeNameList.set(index, vAttributeName); + } + + /** + * + * + * @param vAttributeNameArray + */ + public void setAttributeName(final java.lang.String[] vAttributeNameArray) + { + // -- copy array + _attributeNameList.clear(); + + for (int i = 0; i < vAttributeNameArray.length; i++) + { + this._attributeNameList.add(vAttributeNameArray[i]); + } + } + + /** * Sets the value of field 'autoScale'. * * @param autoScale @@ -566,6 +775,19 @@ public class Setting implements java.io.Serializable } /** + * Sets the value of field 'matcherSet'. The field 'matcherSet' has the + * following description: optional filter(s) applied to the feature type + * + * @param matcherSet + * the value of field 'matcherSet'. + */ + public void setMatcherSet( + final jalview.schemabinding.version2.MatcherSet matcherSet) + { + this._matcherSet = matcherSet; + } + + /** * Sets the value of field 'max'. * * @param max @@ -604,6 +826,18 @@ public class Setting implements java.io.Serializable } /** + * Sets the value of field 'noValueColour'. + * + * @param noValueColour + * the value of field 'noValueColour'. + */ + public void setNoValueColour( + final jalview.schemabinding.version2.types.NoValueColour noValueColour) + { + this._noValueColour = noValueColour; + } + + /** * Sets the value of field 'order'. * * @param order diff --git a/src/jalview/schemabinding/version2/descriptors/ColourDescriptor.java b/src/jalview/schemabinding/version2/descriptors/ColourDescriptor.java index 8b1ae9e..cca4ef1 100644 --- a/src/jalview/schemabinding/version2/descriptors/ColourDescriptor.java +++ b/src/jalview/schemabinding/version2/descriptors/ColourDescriptor.java @@ -18,8 +18,8 @@ import jalview.schemabinding.version2.Colour; * * @version $Revision$ $Date$ */ -public class ColourDescriptor extends - org.exolab.castor.xml.util.XMLClassDescriptorImpl +public class ColourDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl { // --------------------------/ @@ -55,6 +55,9 @@ public class ColourDescriptor extends super(); _xmlName = "colour"; _elementDefinition = true; + + // -- set grouping compositor + setCompositorAsSequence(); org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; org.exolab.castor.mapping.FieldHandler handler = null; org.exolab.castor.xml.FieldValidator fieldValidator = null; @@ -197,11 +200,57 @@ public class ColourDescriptor extends typeValidator.setWhiteSpace("preserve"); } desc.setValidator(fieldValidator); - // -- _threshType + // -- _noValueColour desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( - java.lang.String.class, "_threshType", "threshType", + jalview.schemabinding.version2.types.NoValueColour.class, + "_noValueColour", "noValueColour", org.exolab.castor.xml.NodeType.Attribute); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + Colour target = (Colour) object; + return target.getNoValueColour(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + Colour target = (Colour) object; + target.setNoValueColour( + (jalview.schemabinding.version2.types.NoValueColour) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + handler = new org.exolab.castor.xml.handlers.EnumFieldHandler( + jalview.schemabinding.version2.types.NoValueColour.class, + handler); desc.setImmutable(true); + desc.setHandler(handler); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _noValueColour + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + { // -- local scope + } + desc.setValidator(fieldValidator); + // -- _threshType + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + jalview.schemabinding.version2.types.ColourThreshTypeType.class, + "_threshType", "threshType", + org.exolab.castor.xml.NodeType.Attribute); handler = new org.exolab.castor.xml.XMLFieldHandler() { public java.lang.Object getValue(java.lang.Object object) @@ -217,7 +266,8 @@ public class ColourDescriptor extends try { Colour target = (Colour) object; - target.setThreshType((java.lang.String) value); + target.setThreshType( + (jalview.schemabinding.version2.types.ColourThreshTypeType) value); } catch (java.lang.Exception ex) { throw new IllegalStateException(ex.toString()); @@ -229,6 +279,10 @@ public class ColourDescriptor extends return null; } }; + handler = new org.exolab.castor.xml.handlers.EnumFieldHandler( + jalview.schemabinding.version2.types.ColourThreshTypeType.class, + handler); + desc.setImmutable(true); desc.setHandler(handler); desc.setMultivalued(false); addFieldDescriptor(desc); @@ -236,10 +290,6 @@ public class ColourDescriptor extends // -- validation code for: _threshType fieldValidator = new org.exolab.castor.xml.FieldValidator(); { // -- local scope - org.exolab.castor.xml.validators.StringValidator typeValidator; - typeValidator = new org.exolab.castor.xml.validators.StringValidator(); - fieldValidator.setValidator(typeValidator); - typeValidator.setWhiteSpace("preserve"); } desc.setValidator(fieldValidator); // -- _threshold @@ -437,8 +487,8 @@ public class ColourDescriptor extends target.deleteColourByLabel(); return; } - target.setColourByLabel(((java.lang.Boolean) value) - .booleanValue()); + target.setColourByLabel( + ((java.lang.Boolean) value).booleanValue()); } catch (java.lang.Exception ex) { throw new IllegalStateException(ex.toString()); @@ -518,6 +568,66 @@ public class ColourDescriptor extends desc.setValidator(fieldValidator); // -- initialize element descriptors + // -- _attributeNameList + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + java.lang.String.class, "_attributeNameList", "attributeName", + org.exolab.castor.xml.NodeType.Element); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + Colour target = (Colour) object; + return target.getAttributeName(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + Colour target = (Colour) object; + target.addAttributeName((java.lang.String) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public void resetValue(Object object) + throws IllegalStateException, IllegalArgumentException + { + try + { + Colour target = (Colour) object; + target.removeAllAttributeName(); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + desc.setHandler(handler); + desc.setMultivalued(true); + addFieldDescriptor(desc); + + // -- validation code for: _attributeNameList + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(0); + fieldValidator.setMaxOccurs(2); + { // -- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); } // -----------/ diff --git a/src/jalview/schemabinding/version2/descriptors/CompoundMatcherDescriptor.java b/src/jalview/schemabinding/version2/descriptors/CompoundMatcherDescriptor.java new file mode 100644 index 0000000..2402d68 --- /dev/null +++ b/src/jalview/schemabinding/version2/descriptors/CompoundMatcherDescriptor.java @@ -0,0 +1,270 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.descriptors; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.CompoundMatcher; + +/** + * Class CompoundMatcherDescriptor. + * + * @version $Revision$ $Date$ + */ +public class CompoundMatcherDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public CompoundMatcherDescriptor() + { + super(); + _xmlName = "compoundMatcher"; + _elementDefinition = true; + + // -- set grouping compositor + setCompositorAsSequence(); + org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; + org.exolab.castor.mapping.FieldHandler handler = null; + org.exolab.castor.xml.FieldValidator fieldValidator = null; + // -- initialize attribute descriptors + + // -- _and + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + java.lang.Boolean.TYPE, "_and", "and", + org.exolab.castor.xml.NodeType.Attribute); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + CompoundMatcher target = (CompoundMatcher) object; + if (!target.hasAnd()) + { + return null; + } + return (target.getAnd() ? java.lang.Boolean.TRUE + : java.lang.Boolean.FALSE); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + CompoundMatcher target = (CompoundMatcher) object; + // ignore null values for non optional primitives + if (value == null) + { + return; + } + + target.setAnd(((java.lang.Boolean) value).booleanValue()); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _and + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { // -- local scope + org.exolab.castor.xml.validators.BooleanValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.BooleanValidator(); + fieldValidator.setValidator(typeValidator); + } + desc.setValidator(fieldValidator); + // -- initialize element descriptors + + // -- _matcherSetList + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + jalview.schemabinding.version2.MatcherSet.class, + "_matcherSetList", "matcherSet", + org.exolab.castor.xml.NodeType.Element); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + CompoundMatcher target = (CompoundMatcher) object; + return target.getMatcherSet(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + CompoundMatcher target = (CompoundMatcher) object; + target.addMatcherSet( + (jalview.schemabinding.version2.MatcherSet) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public void resetValue(Object object) + throws IllegalStateException, IllegalArgumentException + { + try + { + CompoundMatcher target = (CompoundMatcher) object; + target.removeAllMatcherSet(); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return new jalview.schemabinding.version2.MatcherSet(); + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(true); + addFieldDescriptor(desc); + + // -- validation code for: _matcherSetList + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(2); + fieldValidator.setMaxOccurs(2); + { // -- local scope + } + desc.setValidator(fieldValidator); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode() + { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity() + { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass() + { + return jalview.schemabinding.version2.CompoundMatcher.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix() + { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI() + { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator() + { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName() + { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition() + { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/descriptors/FeatureMatcherDescriptor.java b/src/jalview/schemabinding/version2/descriptors/FeatureMatcherDescriptor.java new file mode 100644 index 0000000..2df2f5b --- /dev/null +++ b/src/jalview/schemabinding/version2/descriptors/FeatureMatcherDescriptor.java @@ -0,0 +1,356 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.descriptors; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.FeatureMatcher; + +/** + * Class FeatureMatcherDescriptor. + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcherDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public FeatureMatcherDescriptor() + { + super(); + _nsURI = "www.jalview.org/colours"; + _xmlName = "FeatureMatcher"; + _elementDefinition = false; + + // -- set grouping compositor + setCompositorAsSequence(); + org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; + org.exolab.castor.mapping.FieldHandler handler = null; + org.exolab.castor.xml.FieldValidator fieldValidator = null; + // -- initialize attribute descriptors + + // -- _by + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + jalview.schemabinding.version2.types.FeatureMatcherByType.class, + "_by", "by", org.exolab.castor.xml.NodeType.Attribute); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + FeatureMatcher target = (FeatureMatcher) object; + return target.getBy(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + FeatureMatcher target = (FeatureMatcher) object; + target.setBy( + (jalview.schemabinding.version2.types.FeatureMatcherByType) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + handler = new org.exolab.castor.xml.handlers.EnumFieldHandler( + jalview.schemabinding.version2.types.FeatureMatcherByType.class, + handler); + desc.setImmutable(true); + desc.setHandler(handler); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _by + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + { // -- local scope + } + desc.setValidator(fieldValidator); + // -- initialize element descriptors + + // -- _attributeNameList + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + java.lang.String.class, "_attributeNameList", "attributeName", + org.exolab.castor.xml.NodeType.Element); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + FeatureMatcher target = (FeatureMatcher) object; + return target.getAttributeName(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + FeatureMatcher target = (FeatureMatcher) object; + target.addAttributeName((java.lang.String) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public void resetValue(Object object) + throws IllegalStateException, IllegalArgumentException + { + try + { + FeatureMatcher target = (FeatureMatcher) object; + target.removeAllAttributeName(); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + desc.setHandler(handler); + desc.setMultivalued(true); + addFieldDescriptor(desc); + + // -- validation code for: _attributeNameList + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(0); + fieldValidator.setMaxOccurs(2); + { // -- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); + // -- _condition + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + java.lang.String.class, "_condition", "condition", + org.exolab.castor.xml.NodeType.Element); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + FeatureMatcher target = (FeatureMatcher) object; + return target.getCondition(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + FeatureMatcher target = (FeatureMatcher) object; + target.setCondition((java.lang.String) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _condition + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { // -- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); + // -- _value + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + java.lang.String.class, "_value", "value", + org.exolab.castor.xml.NodeType.Element); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + FeatureMatcher target = (FeatureMatcher) object; + return target.getValue(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + FeatureMatcher target = (FeatureMatcher) object; + target.setValue((java.lang.String) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _value + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { // -- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode() + { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity() + { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass() + { + return jalview.schemabinding.version2.FeatureMatcher.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix() + { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI() + { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator() + { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName() + { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition() + { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/descriptors/FeatureMatcherSetDescriptor.java b/src/jalview/schemabinding/version2/descriptors/FeatureMatcherSetDescriptor.java new file mode 100644 index 0000000..b3d19bb --- /dev/null +++ b/src/jalview/schemabinding/version2/descriptors/FeatureMatcherSetDescriptor.java @@ -0,0 +1,258 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.descriptors; + +import jalview.schemabinding.version2.CompoundMatcher; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.FeatureMatcherSet; +import jalview.schemabinding.version2.MatchCondition; + +/** + * Class FeatureMatcherSetDescriptor. + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcherSetDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public FeatureMatcherSetDescriptor() + { + super(); + _nsURI = "www.jalview.org/colours"; + _xmlName = "FeatureMatcherSet"; + _elementDefinition = false; + + // -- set grouping compositor + setCompositorAsChoice(); + org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; + org.exolab.castor.mapping.FieldHandler handler = null; + org.exolab.castor.xml.FieldValidator fieldValidator = null; + // -- initialize attribute descriptors + + // -- initialize element descriptors + + // -- _matchCondition + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + MatchCondition.class, "_matchCondition", "matchCondition", + org.exolab.castor.xml.NodeType.Element); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + @Override + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + FeatureMatcherSet target = (FeatureMatcherSet) object; + return target.getMatchCondition(); + } + + @Override + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + FeatureMatcherSet target = (FeatureMatcherSet) object; + target.setMatchCondition((MatchCondition) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + @Override + public java.lang.Object newInstance(java.lang.Object parent) + { + return new MatchCondition(); + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _matchCondition + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { // -- local scope + } + desc.setValidator(fieldValidator); + // -- _compoundMatcher + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + CompoundMatcher.class, "_compoundMatcher", "compoundMatcher", + org.exolab.castor.xml.NodeType.Element); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + @Override + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + FeatureMatcherSet target = (FeatureMatcherSet) object; + return target.getCompoundMatcher(); + } + + @Override + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + FeatureMatcherSet target = (FeatureMatcherSet) object; + target.setCompoundMatcher((CompoundMatcher) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + @Override + public java.lang.Object newInstance(java.lang.Object parent) + { + return new CompoundMatcher(); + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _compoundMatcher + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { // -- local scope + } + desc.setValidator(fieldValidator); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + @Override + public org.exolab.castor.mapping.AccessMode getAccessMode() + { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no identity. + */ + @Override + public org.exolab.castor.mapping.FieldDescriptor getIdentity() + { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + @Override + public java.lang.Class getJavaClass() + { + return jalview.schemabinding.version2.FeatureMatcherSet.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + @Override + public java.lang.String getNameSpacePrefix() + { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and unmarshaling as XML. + */ + @Override + public java.lang.String getNameSpaceURI() + { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + @Override + public org.exolab.castor.xml.TypeValidator getValidator() + { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + @Override + public java.lang.String getXMLName() + { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that of a global + * element or element with anonymous type definition. + */ + @Override + public boolean isElementDefinition() + { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/descriptors/FilterDescriptor.java b/src/jalview/schemabinding/version2/descriptors/FilterDescriptor.java new file mode 100644 index 0000000..f58f9ae --- /dev/null +++ b/src/jalview/schemabinding/version2/descriptors/FilterDescriptor.java @@ -0,0 +1,246 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.descriptors; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.Filter; + +/** + * Class FilterDescriptor. + * + * @version $Revision$ $Date$ + */ +public class FilterDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public FilterDescriptor() + { + super(); + _xmlName = "filter"; + _elementDefinition = true; + + // -- set grouping compositor + setCompositorAsSequence(); + org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; + org.exolab.castor.mapping.FieldHandler handler = null; + org.exolab.castor.xml.FieldValidator fieldValidator = null; + // -- initialize attribute descriptors + + // -- _featureType + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + java.lang.String.class, "_featureType", "featureType", + org.exolab.castor.xml.NodeType.Attribute); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + Filter target = (Filter) object; + return target.getFeatureType(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + Filter target = (Filter) object; + target.setFeatureType((java.lang.String) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _featureType + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { // -- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); + // -- initialize element descriptors + + // -- _matcherSet + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + jalview.schemabinding.version2.MatcherSet.class, "_matcherSet", + "matcherSet", org.exolab.castor.xml.NodeType.Element); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + Filter target = (Filter) object; + return target.getMatcherSet(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + Filter target = (Filter) object; + target.setMatcherSet( + (jalview.schemabinding.version2.MatcherSet) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return new jalview.schemabinding.version2.MatcherSet(); + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _matcherSet + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { // -- local scope + } + desc.setValidator(fieldValidator); + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode() + { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity() + { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass() + { + return jalview.schemabinding.version2.Filter.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix() + { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI() + { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator() + { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName() + { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition() + { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/descriptors/JalviewUserColoursDescriptor.java b/src/jalview/schemabinding/version2/descriptors/JalviewUserColoursDescriptor.java index d65de13..459d645 100644 --- a/src/jalview/schemabinding/version2/descriptors/JalviewUserColoursDescriptor.java +++ b/src/jalview/schemabinding/version2/descriptors/JalviewUserColoursDescriptor.java @@ -7,11 +7,13 @@ package jalview.schemabinding.version2.descriptors; +import jalview.schemabinding.version2.Colour; +import jalview.schemabinding.version2.Filter; + //---------------------------------/ //- Imported classes and packages -/ //---------------------------------/ -import jalview.schemabinding.version2.Colour; import jalview.schemabinding.version2.JalviewUserColours; /** @@ -19,8 +21,8 @@ import jalview.schemabinding.version2.JalviewUserColours; * * @version $Revision$ $Date$ */ -public class JalviewUserColoursDescriptor extends - org.exolab.castor.xml.util.XMLClassDescriptorImpl +public class JalviewUserColoursDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl { // --------------------------/ @@ -192,8 +194,8 @@ public class JalviewUserColoursDescriptor extends } @Override - public void resetValue(Object object) throws IllegalStateException, - IllegalArgumentException + public void resetValue(Object object) + throws IllegalStateException, IllegalArgumentException { try { @@ -221,6 +223,64 @@ public class JalviewUserColoursDescriptor extends { // -- local scope } desc.setValidator(fieldValidator); + // -- _filterList + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + Filter.class, "_filterList", "filter", + org.exolab.castor.xml.NodeType.Element); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + @Override + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + JalviewUserColours target = (JalviewUserColours) object; + return target.getFilter(); + } + + @Override + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + JalviewUserColours target = (JalviewUserColours) object; + target.addFilter((Filter) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + @Override + public void resetValue(Object object) + throws IllegalStateException, IllegalArgumentException + { + try + { + JalviewUserColours target = (JalviewUserColours) object; + target.removeAllFilter(); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + @Override + public java.lang.Object newInstance(java.lang.Object parent) + { + return new Filter(); + } + }; + desc.setHandler(handler); + desc.setMultivalued(true); + addFieldDescriptor(desc); + + // -- validation code for: _filterList + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(0); + { // -- local scope + } + desc.setValidator(fieldValidator); } // -----------/ diff --git a/src/jalview/schemabinding/version2/descriptors/MatchConditionDescriptor.java b/src/jalview/schemabinding/version2/descriptors/MatchConditionDescriptor.java new file mode 100644 index 0000000..8373421 --- /dev/null +++ b/src/jalview/schemabinding/version2/descriptors/MatchConditionDescriptor.java @@ -0,0 +1,148 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.descriptors; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.MatchCondition; + +/** + * Class MatchConditionDescriptor. + * + * @version $Revision$ $Date$ + */ +public class MatchConditionDescriptor extends + jalview.schemabinding.version2.descriptors.FeatureMatcherDescriptor +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public MatchConditionDescriptor() + { + super(); + setExtendsWithoutFlatten( + new jalview.schemabinding.version2.descriptors.FeatureMatcherDescriptor()); + _xmlName = "matchCondition"; + _elementDefinition = true; + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode() + { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity() + { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass() + { + return jalview.schemabinding.version2.MatchCondition.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix() + { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI() + { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator() + { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName() + { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition() + { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/descriptors/MatcherSetDescriptor.java b/src/jalview/schemabinding/version2/descriptors/MatcherSetDescriptor.java new file mode 100644 index 0000000..2807f92 --- /dev/null +++ b/src/jalview/schemabinding/version2/descriptors/MatcherSetDescriptor.java @@ -0,0 +1,148 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.descriptors; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.MatcherSet; + +/** + * Class MatcherSetDescriptor. + * + * @version $Revision$ $Date$ + */ +public class MatcherSetDescriptor extends + jalview.schemabinding.version2.descriptors.FeatureMatcherSetDescriptor +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public MatcherSetDescriptor() + { + super(); + setExtendsWithoutFlatten( + new jalview.schemabinding.version2.descriptors.FeatureMatcherSetDescriptor()); + _xmlName = "matcherSet"; + _elementDefinition = true; + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode() + { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity() + { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass() + { + return jalview.schemabinding.version2.MatcherSet.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix() + { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI() + { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator() + { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName() + { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition() + { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/descriptors/OtherDataDescriptor.java b/src/jalview/schemabinding/version2/descriptors/OtherDataDescriptor.java index f582311..ab7a626 100644 --- a/src/jalview/schemabinding/version2/descriptors/OtherDataDescriptor.java +++ b/src/jalview/schemabinding/version2/descriptors/OtherDataDescriptor.java @@ -7,8 +7,8 @@ package jalview.schemabinding.version2.descriptors; -//---------------------------------/ -//- Imported classes and packages -/ + //---------------------------------/ + //- Imported classes and packages -/ //---------------------------------/ import jalview.schemabinding.version2.OtherData; @@ -18,231 +18,255 @@ import jalview.schemabinding.version2.OtherData; * * @version $Revision$ $Date$ */ -public class OtherDataDescriptor extends - org.exolab.castor.xml.util.XMLClassDescriptorImpl -{ - - // --------------------------/ - // - Class/Member Variables -/ - // --------------------------/ - - /** - * Field _elementDefinition. - */ - private boolean _elementDefinition; - - /** - * Field _nsPrefix. - */ - private java.lang.String _nsPrefix; - - /** - * Field _nsURI. - */ - private java.lang.String _nsURI; - - /** - * Field _xmlName. - */ - private java.lang.String _xmlName; - - // ----------------/ - // - Constructors -/ - // ----------------/ - - public OtherDataDescriptor() - { - super(); - _nsURI = "www.jalview.org"; - _xmlName = "otherData"; - _elementDefinition = true; - org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; - org.exolab.castor.mapping.FieldHandler handler = null; - org.exolab.castor.xml.FieldValidator fieldValidator = null; - // -- initialize attribute descriptors - - // -- _key - desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( - java.lang.String.class, "_key", "key", - org.exolab.castor.xml.NodeType.Attribute); - desc.setImmutable(true); - handler = new org.exolab.castor.xml.XMLFieldHandler() - { - public java.lang.Object getValue(java.lang.Object object) - throws IllegalStateException - { - OtherData target = (OtherData) object; - return target.getKey(); - } - - public void setValue(java.lang.Object object, java.lang.Object value) - throws IllegalStateException, IllegalArgumentException - { - try - { - OtherData target = (OtherData) object; - target.setKey((java.lang.String) value); - } catch (java.lang.Exception ex) - { - throw new IllegalStateException(ex.toString()); +public class OtherDataDescriptor extends org.exolab.castor.xml.util.XMLClassDescriptorImpl { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + + //----------------/ + //- Constructors -/ + //----------------/ + + public OtherDataDescriptor() { + super(); + _nsURI = "www.jalview.org"; + _xmlName = "otherData"; + _elementDefinition = true; + org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; + org.exolab.castor.mapping.FieldHandler handler = null; + org.exolab.castor.xml.FieldValidator fieldValidator = null; + //-- initialize attribute descriptors + + //-- _key + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl(java.lang.String.class, "_key", "key", org.exolab.castor.xml.NodeType.Attribute); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() { + public java.lang.Object getValue( java.lang.Object object ) + throws IllegalStateException + { + OtherData target = (OtherData) object; + return target.getKey(); + } + public void setValue( java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try { + OtherData target = (OtherData) object; + target.setKey( (java.lang.String) value); + } catch (java.lang.Exception ex) { + throw new IllegalStateException(ex.toString()); + } + } + public java.lang.Object newInstance(java.lang.Object parent) { + return null; + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + //-- validation code for: _key + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { //-- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); + //-- _key2 + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl(java.lang.String.class, "_key2", "key2", org.exolab.castor.xml.NodeType.Attribute); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() { + public java.lang.Object getValue( java.lang.Object object ) + throws IllegalStateException + { + OtherData target = (OtherData) object; + return target.getKey2(); + } + public void setValue( java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try { + OtherData target = (OtherData) object; + target.setKey2( (java.lang.String) value); + } catch (java.lang.Exception ex) { + throw new IllegalStateException(ex.toString()); + } + } + public java.lang.Object newInstance(java.lang.Object parent) { + return null; + } + }; + desc.setHandler(handler); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + //-- validation code for: _key2 + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + { //-- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); + //-- _value + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl(java.lang.String.class, "_value", "value", org.exolab.castor.xml.NodeType.Attribute); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() { + public java.lang.Object getValue( java.lang.Object object ) + throws IllegalStateException + { + OtherData target = (OtherData) object; + return target.getValue(); + } + public void setValue( java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try { + OtherData target = (OtherData) object; + target.setValue( (java.lang.String) value); + } catch (java.lang.Exception ex) { + throw new IllegalStateException(ex.toString()); + } + } + public java.lang.Object newInstance(java.lang.Object parent) { + return null; + } + }; + desc.setHandler(handler); + desc.setRequired(true); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + //-- validation code for: _value + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(1); + { //-- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); } - } + desc.setValidator(fieldValidator); + //-- initialize element descriptors + + } + + + //-----------/ + //- Methods -/ + //-----------/ - public java.lang.Object newInstance(java.lang.Object parent) - { + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode( + ) { return null; - } - }; - desc.setHandler(handler); - desc.setRequired(true); - desc.setMultivalued(false); - addFieldDescriptor(desc); - - // -- validation code for: _key - fieldValidator = new org.exolab.castor.xml.FieldValidator(); - fieldValidator.setMinOccurs(1); - { // -- local scope - org.exolab.castor.xml.validators.StringValidator typeValidator; - typeValidator = new org.exolab.castor.xml.validators.StringValidator(); - fieldValidator.setValidator(typeValidator); - typeValidator.setWhiteSpace("preserve"); } - desc.setValidator(fieldValidator); - // -- _value - desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( - java.lang.String.class, "_value", "value", - org.exolab.castor.xml.NodeType.Attribute); - desc.setImmutable(true); - handler = new org.exolab.castor.xml.XMLFieldHandler() - { - public java.lang.Object getValue(java.lang.Object object) - throws IllegalStateException - { - OtherData target = (OtherData) object; - return target.getValue(); - } - - public void setValue(java.lang.Object object, java.lang.Object value) - throws IllegalStateException, IllegalArgumentException - { - try - { - OtherData target = (OtherData) object; - target.setValue((java.lang.String) value); - } catch (java.lang.Exception ex) - { - throw new IllegalStateException(ex.toString()); - } - } - public java.lang.Object newInstance(java.lang.Object parent) - { - return null; - } - }; - desc.setHandler(handler); - desc.setRequired(true); - desc.setMultivalued(false); - addFieldDescriptor(desc); - - // -- validation code for: _value - fieldValidator = new org.exolab.castor.xml.FieldValidator(); - fieldValidator.setMinOccurs(1); - { // -- local scope - org.exolab.castor.xml.validators.StringValidator typeValidator; - typeValidator = new org.exolab.castor.xml.validators.StringValidator(); - fieldValidator.setValidator(typeValidator); - typeValidator.setWhiteSpace("preserve"); + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no + * identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity( + ) { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass( + ) { + return jalview.schemabinding.version2.OtherData.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix( + ) { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and + * unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI( + ) { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator( + ) { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName( + ) { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that + * of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition( + ) { + return _elementDefinition; } - desc.setValidator(fieldValidator); - // -- initialize element descriptors - - } - - // -----------/ - // - Methods -/ - // -----------/ - - /** - * Method getAccessMode. - * - * @return the access mode specified for this class. - */ - public org.exolab.castor.mapping.AccessMode getAccessMode() - { - return null; - } - - /** - * Method getIdentity. - * - * @return the identity field, null if this class has no identity. - */ - public org.exolab.castor.mapping.FieldDescriptor getIdentity() - { - return super.getIdentity(); - } - - /** - * Method getJavaClass. - * - * @return the Java class represented by this descriptor. - */ - public java.lang.Class getJavaClass() - { - return jalview.schemabinding.version2.OtherData.class; - } - - /** - * Method getNameSpacePrefix. - * - * @return the namespace prefix to use when marshaling as XML. - */ - public java.lang.String getNameSpacePrefix() - { - return _nsPrefix; - } - - /** - * Method getNameSpaceURI. - * - * @return the namespace URI used when marshaling and unmarshaling as XML. - */ - public java.lang.String getNameSpaceURI() - { - return _nsURI; - } - - /** - * Method getValidator. - * - * @return a specific validator for the class described by this - * ClassDescriptor. - */ - public org.exolab.castor.xml.TypeValidator getValidator() - { - return this; - } - - /** - * Method getXMLName. - * - * @return the XML Name for the Class being described. - */ - public java.lang.String getXMLName() - { - return _xmlName; - } - - /** - * Method isElementDefinition. - * - * @return true if XML schema definition of this Class is that of a global - * element or element with anonymous type definition. - */ - public boolean isElementDefinition() - { - return _elementDefinition; - } } diff --git a/src/jalview/schemabinding/version2/descriptors/SettingDescriptor.java b/src/jalview/schemabinding/version2/descriptors/SettingDescriptor.java index 4703f46..c816e43 100644 --- a/src/jalview/schemabinding/version2/descriptors/SettingDescriptor.java +++ b/src/jalview/schemabinding/version2/descriptors/SettingDescriptor.java @@ -18,8 +18,8 @@ import jalview.schemabinding.version2.Setting; * * @version $Revision$ $Date$ */ -public class SettingDescriptor extends - org.exolab.castor.xml.util.XMLClassDescriptorImpl +public class SettingDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl { // --------------------------/ @@ -56,6 +56,9 @@ public class SettingDescriptor extends _nsURI = "www.jalview.org"; _xmlName = "setting"; _elementDefinition = true; + + // -- set grouping compositor + setCompositorAsSequence(); org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null; org.exolab.castor.mapping.FieldHandler handler = null; org.exolab.castor.xml.FieldValidator fieldValidator = null; @@ -331,6 +334,52 @@ public class SettingDescriptor extends typeValidator.setMaxInclusive(2147483647); } desc.setValidator(fieldValidator); + // -- _noValueColour + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + jalview.schemabinding.version2.types.NoValueColour.class, + "_noValueColour", "noValueColour", + org.exolab.castor.xml.NodeType.Attribute); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + Setting target = (Setting) object; + return target.getNoValueColour(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + Setting target = (Setting) object; + target.setNoValueColour( + (jalview.schemabinding.version2.types.NoValueColour) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + handler = new org.exolab.castor.xml.handlers.EnumFieldHandler( + jalview.schemabinding.version2.types.NoValueColour.class, + handler); + desc.setImmutable(true); + desc.setHandler(handler); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _noValueColour + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + { // -- local scope + } + desc.setValidator(fieldValidator); // -- _threshold desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( java.lang.Float.TYPE, "_threshold", "threshold", @@ -581,8 +630,8 @@ public class SettingDescriptor extends target.deleteColourByLabel(); return; } - target.setColourByLabel(((java.lang.Boolean) value) - .booleanValue()); + target.setColourByLabel( + ((java.lang.Boolean) value).booleanValue()); } catch (java.lang.Exception ex) { throw new IllegalStateException(ex.toString()); @@ -662,6 +711,109 @@ public class SettingDescriptor extends desc.setValidator(fieldValidator); // -- initialize element descriptors + // -- _attributeNameList + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + java.lang.String.class, "_attributeNameList", "attributeName", + org.exolab.castor.xml.NodeType.Element); + desc.setImmutable(true); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + Setting target = (Setting) object; + return target.getAttributeName(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + Setting target = (Setting) object; + target.addAttributeName((java.lang.String) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public void resetValue(Object object) + throws IllegalStateException, IllegalArgumentException + { + try + { + Setting target = (Setting) object; + target.removeAllAttributeName(); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return null; + } + }; + desc.setHandler(handler); + desc.setNameSpaceURI("www.jalview.org"); + desc.setMultivalued(true); + addFieldDescriptor(desc); + + // -- validation code for: _attributeNameList + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + fieldValidator.setMinOccurs(0); + fieldValidator.setMaxOccurs(2); + { // -- local scope + org.exolab.castor.xml.validators.StringValidator typeValidator; + typeValidator = new org.exolab.castor.xml.validators.StringValidator(); + fieldValidator.setValidator(typeValidator); + typeValidator.setWhiteSpace("preserve"); + } + desc.setValidator(fieldValidator); + // -- _matcherSet + desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl( + jalview.schemabinding.version2.MatcherSet.class, "_matcherSet", + "matcherSet", org.exolab.castor.xml.NodeType.Element); + handler = new org.exolab.castor.xml.XMLFieldHandler() + { + public java.lang.Object getValue(java.lang.Object object) + throws IllegalStateException + { + Setting target = (Setting) object; + return target.getMatcherSet(); + } + + public void setValue(java.lang.Object object, java.lang.Object value) + throws IllegalStateException, IllegalArgumentException + { + try + { + Setting target = (Setting) object; + target.setMatcherSet( + (jalview.schemabinding.version2.MatcherSet) value); + } catch (java.lang.Exception ex) + { + throw new IllegalStateException(ex.toString()); + } + } + + public java.lang.Object newInstance(java.lang.Object parent) + { + return new jalview.schemabinding.version2.MatcherSet(); + } + }; + desc.setHandler(handler); + desc.setNameSpaceURI("www.jalview.org"); + desc.setMultivalued(false); + addFieldDescriptor(desc); + + // -- validation code for: _matcherSet + fieldValidator = new org.exolab.castor.xml.FieldValidator(); + { // -- local scope + } + desc.setValidator(fieldValidator); } // -----------/ diff --git a/src/jalview/schemabinding/version2/types/.castor.cdr b/src/jalview/schemabinding/version2/types/.castor.cdr new file mode 100644 index 0000000..d9874b6 --- /dev/null +++ b/src/jalview/schemabinding/version2/types/.castor.cdr @@ -0,0 +1,5 @@ +#Thu Dec 14 15:28:22 GMT 2017 +jalview.schemabinding.version2.types.ColourNoValueColourType=jalview.schemabinding.version2.types.descriptors.ColourNoValueColourTypeDescriptor +jalview.schemabinding.version2.types.FeatureMatcherByType=jalview.schemabinding.version2.types.descriptors.FeatureMatcherByTypeDescriptor +jalview.schemabinding.version2.types.NoValueColour=jalview.schemabinding.version2.types.descriptors.NoValueColourDescriptor +jalview.schemabinding.version2.types.ColourThreshTypeType=jalview.schemabinding.version2.types.descriptors.ColourThreshTypeTypeDescriptor diff --git a/src/jalview/schemabinding/version2/types/ColourThreshTypeType.java b/src/jalview/schemabinding/version2/types/ColourThreshTypeType.java new file mode 100644 index 0000000..0330411 --- /dev/null +++ b/src/jalview/schemabinding/version2/types/ColourThreshTypeType.java @@ -0,0 +1,168 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.types; + + //---------------------------------/ + //- Imported classes and packages -/ +//---------------------------------/ + +import java.util.Hashtable; + +/** + * Class ColourThreshTypeType. + * + * @version $Revision$ $Date$ + */ +public class ColourThreshTypeType implements java.io.Serializable { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * The NONE type + */ + public static final int NONE_TYPE = 0; + + /** + * The instance of the NONE type + */ + public static final ColourThreshTypeType NONE = new ColourThreshTypeType(NONE_TYPE, "NONE"); + + /** + * The ABOVE type + */ + public static final int ABOVE_TYPE = 1; + + /** + * The instance of the ABOVE type + */ + public static final ColourThreshTypeType ABOVE = new ColourThreshTypeType(ABOVE_TYPE, "ABOVE"); + + /** + * The BELOW type + */ + public static final int BELOW_TYPE = 2; + + /** + * The instance of the BELOW type + */ + public static final ColourThreshTypeType BELOW = new ColourThreshTypeType(BELOW_TYPE, "BELOW"); + + /** + * Field _memberTable. + */ + private static java.util.Hashtable _memberTable = init(); + + /** + * Field type. + */ + private int type = -1; + + /** + * Field stringValue. + */ + private java.lang.String stringValue = null; + + + //----------------/ + //- Constructors -/ + //----------------/ + + private ColourThreshTypeType(final int type, final java.lang.String value) { + super(); + this.type = type; + this.stringValue = value; + } + + + //-----------/ + //- Methods -/ + //-----------/ + + /** + * Method enumerate.Returns an enumeration of all possible + * instances of ColourThreshTypeType + * + * @return an Enumeration over all possible instances of + * ColourThreshTypeType + */ + public static java.util.Enumeration enumerate( + ) { + return _memberTable.elements(); + } + + /** + * Method getType.Returns the type of this ColourThreshTypeType + * + * @return the type of this ColourThreshTypeType + */ + public int getType( + ) { + return this.type; + } + + /** + * Method init. + * + * @return the initialized Hashtable for the member table + */ + private static java.util.Hashtable init( + ) { + Hashtable members = new Hashtable(); + members.put("NONE", NONE); + members.put("ABOVE", ABOVE); + members.put("BELOW", BELOW); + return members; + } + + /** + * Method readResolve. will be called during deserialization to + * replace the deserialized object with the correct constant + * instance. + * + * @return this deserialized object + */ + private java.lang.Object readResolve( + ) { + return valueOf(this.stringValue); + } + + /** + * Method toString.Returns the String representation of this + * ColourThreshTypeType + * + * @return the String representation of this ColourThreshTypeTyp + */ + public java.lang.String toString( + ) { + return this.stringValue; + } + + /** + * Method valueOf.Returns a new ColourThreshTypeType based on + * the given String value. + * + * @param string + * @return the ColourThreshTypeType value of parameter 'string' + */ + public static jalview.schemabinding.version2.types.ColourThreshTypeType valueOf( + final java.lang.String string) { + java.lang.Object obj = null; + if (string != null) { + obj = _memberTable.get(string); + } + if (obj == null) { + String err = "" + string + " is not a valid ColourThreshTypeType"; + throw new IllegalArgumentException(err); + } + return (ColourThreshTypeType) obj; + } + +} diff --git a/src/jalview/schemabinding/version2/types/FeatureMatcherByType.java b/src/jalview/schemabinding/version2/types/FeatureMatcherByType.java new file mode 100644 index 0000000..6e97332 --- /dev/null +++ b/src/jalview/schemabinding/version2/types/FeatureMatcherByType.java @@ -0,0 +1,168 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.types; + + //---------------------------------/ + //- Imported classes and packages -/ +//---------------------------------/ + +import java.util.Hashtable; + +/** + * Class FeatureMatcherByType. + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcherByType implements java.io.Serializable { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * The byLabel type + */ + public static final int BYLABEL_TYPE = 0; + + /** + * The instance of the byLabel type + */ + public static final FeatureMatcherByType BYLABEL = new FeatureMatcherByType(BYLABEL_TYPE, "byLabel"); + + /** + * The byScore type + */ + public static final int BYSCORE_TYPE = 1; + + /** + * The instance of the byScore type + */ + public static final FeatureMatcherByType BYSCORE = new FeatureMatcherByType(BYSCORE_TYPE, "byScore"); + + /** + * The byAttribute type + */ + public static final int BYATTRIBUTE_TYPE = 2; + + /** + * The instance of the byAttribute type + */ + public static final FeatureMatcherByType BYATTRIBUTE = new FeatureMatcherByType(BYATTRIBUTE_TYPE, "byAttribute"); + + /** + * Field _memberTable. + */ + private static java.util.Hashtable _memberTable = init(); + + /** + * Field type. + */ + private int type = -1; + + /** + * Field stringValue. + */ + private java.lang.String stringValue = null; + + + //----------------/ + //- Constructors -/ + //----------------/ + + private FeatureMatcherByType(final int type, final java.lang.String value) { + super(); + this.type = type; + this.stringValue = value; + } + + + //-----------/ + //- Methods -/ + //-----------/ + + /** + * Method enumerate.Returns an enumeration of all possible + * instances of FeatureMatcherByType + * + * @return an Enumeration over all possible instances of + * FeatureMatcherByType + */ + public static java.util.Enumeration enumerate( + ) { + return _memberTable.elements(); + } + + /** + * Method getType.Returns the type of this FeatureMatcherByType + * + * @return the type of this FeatureMatcherByType + */ + public int getType( + ) { + return this.type; + } + + /** + * Method init. + * + * @return the initialized Hashtable for the member table + */ + private static java.util.Hashtable init( + ) { + Hashtable members = new Hashtable(); + members.put("byLabel", BYLABEL); + members.put("byScore", BYSCORE); + members.put("byAttribute", BYATTRIBUTE); + return members; + } + + /** + * Method readResolve. will be called during deserialization to + * replace the deserialized object with the correct constant + * instance. + * + * @return this deserialized object + */ + private java.lang.Object readResolve( + ) { + return valueOf(this.stringValue); + } + + /** + * Method toString.Returns the String representation of this + * FeatureMatcherByType + * + * @return the String representation of this FeatureMatcherByTyp + */ + public java.lang.String toString( + ) { + return this.stringValue; + } + + /** + * Method valueOf.Returns a new FeatureMatcherByType based on + * the given String value. + * + * @param string + * @return the FeatureMatcherByType value of parameter 'string' + */ + public static jalview.schemabinding.version2.types.FeatureMatcherByType valueOf( + final java.lang.String string) { + java.lang.Object obj = null; + if (string != null) { + obj = _memberTable.get(string); + } + if (obj == null) { + String err = "" + string + " is not a valid FeatureMatcherByType"; + throw new IllegalArgumentException(err); + } + return (FeatureMatcherByType) obj; + } + +} diff --git a/src/jalview/schemabinding/version2/types/NoValueColour.java b/src/jalview/schemabinding/version2/types/NoValueColour.java new file mode 100644 index 0000000..bbef3d7 --- /dev/null +++ b/src/jalview/schemabinding/version2/types/NoValueColour.java @@ -0,0 +1,169 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.types; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import java.util.Hashtable; + +/** + * Graduated feature colour if no score (or attribute) value + * + * @version $Revision$ $Date$ + */ +public class NoValueColour implements java.io.Serializable +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * The None type + */ + public static final int NONE_TYPE = 0; + + /** + * The instance of the None type + */ + public static final NoValueColour NONE = new NoValueColour(NONE_TYPE, + "None"); + + /** + * The Min type + */ + public static final int MIN_TYPE = 1; + + /** + * The instance of the Min type + */ + public static final NoValueColour MIN = new NoValueColour(MIN_TYPE, + "Min"); + + /** + * The Max type + */ + public static final int MAX_TYPE = 2; + + /** + * The instance of the Max type + */ + public static final NoValueColour MAX = new NoValueColour(MAX_TYPE, + "Max"); + + /** + * Field _memberTable. + */ + private static java.util.Hashtable _memberTable = init(); + + /** + * Field type. + */ + private int type = -1; + + /** + * Field stringValue. + */ + private java.lang.String stringValue = null; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + private NoValueColour(final int type, final java.lang.String value) + { + super(); + this.type = type; + this.stringValue = value; + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method enumerate.Returns an enumeration of all possible instances of + * NoValueColour + * + * @return an Enumeration over all possible instances of NoValueColour + */ + public static java.util.Enumeration enumerate() + { + return _memberTable.elements(); + } + + /** + * Method getType.Returns the type of this NoValueColour + * + * @return the type of this NoValueColour + */ + public int getType() + { + return this.type; + } + + /** + * Method init. + * + * @return the initialized Hashtable for the member table + */ + private static java.util.Hashtable init() + { + Hashtable members = new Hashtable(); + members.put("None", NONE); + members.put("Min", MIN); + members.put("Max", MAX); + return members; + } + + /** + * Method readResolve. will be called during deserialization to replace the + * deserialized object with the correct constant instance. + * + * @return this deserialized object + */ + private java.lang.Object readResolve() + { + return valueOf(this.stringValue); + } + + /** + * Method toString.Returns the String representation of this NoValueColour + * + * @return the String representation of this NoValueColour + */ + public java.lang.String toString() + { + return this.stringValue; + } + + /** + * Method valueOf.Returns a new NoValueColour based on the given String value. + * + * @param string + * @return the NoValueColour value of parameter 'string' + */ + public static jalview.schemabinding.version2.types.NoValueColour valueOf( + final java.lang.String string) + { + java.lang.Object obj = null; + if (string != null) + { + obj = _memberTable.get(string); + } + if (obj == null) + { + String err = "" + string + " is not a valid NoValueColour"; + throw new IllegalArgumentException(err); + } + return (NoValueColour) obj; + } + +} diff --git a/src/jalview/schemabinding/version2/types/descriptors/ColourThreshTypeTypeDescriptor.java b/src/jalview/schemabinding/version2/types/descriptors/ColourThreshTypeTypeDescriptor.java new file mode 100644 index 0000000..f978363 --- /dev/null +++ b/src/jalview/schemabinding/version2/types/descriptors/ColourThreshTypeTypeDescriptor.java @@ -0,0 +1,150 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.types.descriptors; + + //---------------------------------/ + //- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.types.ColourThreshTypeType; + +/** + * Class ColourThreshTypeTypeDescriptor. + * + * @version $Revision$ $Date$ + */ +public class ColourThreshTypeTypeDescriptor extends org.exolab.castor.xml.util.XMLClassDescriptorImpl { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + + //----------------/ + //- Constructors -/ + //----------------/ + + public ColourThreshTypeTypeDescriptor() { + super(); + _nsURI = "www.jalview.org/colours"; + _xmlName = "ColourThreshTypeType"; + _elementDefinition = false; + } + + + //-----------/ + //- Methods -/ + //-----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode( + ) { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no + * identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity( + ) { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass( + ) { + return jalview.schemabinding.version2.types.ColourThreshTypeType.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix( + ) { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and + * unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI( + ) { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator( + ) { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName( + ) { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that + * of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition( + ) { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/types/descriptors/FeatureMatcherByTypeDescriptor.java b/src/jalview/schemabinding/version2/types/descriptors/FeatureMatcherByTypeDescriptor.java new file mode 100644 index 0000000..e392e76 --- /dev/null +++ b/src/jalview/schemabinding/version2/types/descriptors/FeatureMatcherByTypeDescriptor.java @@ -0,0 +1,150 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.types.descriptors; + + //---------------------------------/ + //- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.types.FeatureMatcherByType; + +/** + * Class FeatureMatcherByTypeDescriptor. + * + * @version $Revision$ $Date$ + */ +public class FeatureMatcherByTypeDescriptor extends org.exolab.castor.xml.util.XMLClassDescriptorImpl { + + + //--------------------------/ + //- Class/Member Variables -/ + //--------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + + //----------------/ + //- Constructors -/ + //----------------/ + + public FeatureMatcherByTypeDescriptor() { + super(); + _nsURI = "www.jalview.org/colours"; + _xmlName = "FeatureMatcherByType"; + _elementDefinition = false; + } + + + //-----------/ + //- Methods -/ + //-----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode( + ) { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no + * identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity( + ) { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass( + ) { + return jalview.schemabinding.version2.types.FeatureMatcherByType.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix( + ) { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and + * unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI( + ) { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator( + ) { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName( + ) { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that + * of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition( + ) { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemabinding/version2/types/descriptors/NoValueColourDescriptor.java b/src/jalview/schemabinding/version2/types/descriptors/NoValueColourDescriptor.java new file mode 100644 index 0000000..14c58ed --- /dev/null +++ b/src/jalview/schemabinding/version2/types/descriptors/NoValueColourDescriptor.java @@ -0,0 +1,147 @@ +/* + * This class was automatically generated with + * Castor 1.1, using an XML + * Schema. + * $Id$ + */ + +package jalview.schemabinding.version2.types.descriptors; + +//---------------------------------/ +//- Imported classes and packages -/ +//---------------------------------/ + +import jalview.schemabinding.version2.types.NoValueColour; + +/** + * Class NoValueColourDescriptor. + * + * @version $Revision$ $Date$ + */ +public class NoValueColourDescriptor + extends org.exolab.castor.xml.util.XMLClassDescriptorImpl +{ + + // --------------------------/ + // - Class/Member Variables -/ + // --------------------------/ + + /** + * Field _elementDefinition. + */ + private boolean _elementDefinition; + + /** + * Field _nsPrefix. + */ + private java.lang.String _nsPrefix; + + /** + * Field _nsURI. + */ + private java.lang.String _nsURI; + + /** + * Field _xmlName. + */ + private java.lang.String _xmlName; + + // ----------------/ + // - Constructors -/ + // ----------------/ + + public NoValueColourDescriptor() + { + super(); + _nsURI = "www.jalview.org/colours"; + _xmlName = "NoValueColour"; + _elementDefinition = false; + } + + // -----------/ + // - Methods -/ + // -----------/ + + /** + * Method getAccessMode. + * + * @return the access mode specified for this class. + */ + public org.exolab.castor.mapping.AccessMode getAccessMode() + { + return null; + } + + /** + * Method getIdentity. + * + * @return the identity field, null if this class has no identity. + */ + public org.exolab.castor.mapping.FieldDescriptor getIdentity() + { + return super.getIdentity(); + } + + /** + * Method getJavaClass. + * + * @return the Java class represented by this descriptor. + */ + public java.lang.Class getJavaClass() + { + return jalview.schemabinding.version2.types.NoValueColour.class; + } + + /** + * Method getNameSpacePrefix. + * + * @return the namespace prefix to use when marshaling as XML. + */ + public java.lang.String getNameSpacePrefix() + { + return _nsPrefix; + } + + /** + * Method getNameSpaceURI. + * + * @return the namespace URI used when marshaling and unmarshaling as XML. + */ + public java.lang.String getNameSpaceURI() + { + return _nsURI; + } + + /** + * Method getValidator. + * + * @return a specific validator for the class described by this + * ClassDescriptor. + */ + public org.exolab.castor.xml.TypeValidator getValidator() + { + return this; + } + + /** + * Method getXMLName. + * + * @return the XML Name for the Class being described. + */ + public java.lang.String getXMLName() + { + return _xmlName; + } + + /** + * Method isElementDefinition. + * + * @return true if XML schema definition of this Class is that of a global + * element or element with anonymous type definition. + */ + public boolean isElementDefinition() + { + return _elementDefinition; + } + +} diff --git a/src/jalview/schemes/FeatureColour.java b/src/jalview/schemes/FeatureColour.java index 54d1c6c..51e7645 100644 --- a/src/jalview/schemes/FeatureColour.java +++ b/src/jalview/schemes/FeatureColour.java @@ -22,6 +22,7 @@ package jalview.schemes; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; +import jalview.datamodel.features.FeatureMatcher; import jalview.util.ColorUtils; import jalview.util.Format; @@ -29,10 +30,49 @@ import java.awt.Color; import java.util.StringTokenizer; /** - * A class that wraps either a simple colour or a graduated colour + * A class that represents a colour scheme for a feature type. Options supported + * are currently + *

              + *
            • a simple colour e.g. Red
            • + *
            • colour by label - a colour is generated from the feature description
            • + *
            • graduated colour by feature score
            • + *
                + *
              • minimum and maximum score range must be provided
              • + *
              • minimum and maximum value colours should be specified
              • + *
              • a colour for 'no value' may optionally be provided
              • + *
              • colours for intermediate scores are interpolated RGB values
              • + *
              • there is an optional threshold above/below which to colour values
              • + *
              • the range may be the full value range, or may be limited by the threshold + * value
              • + *
              + *
            • colour by (text) value of a named attribute
            • graduated colour by + * (numeric) value of a named attribute
            */ public class FeatureColour implements FeatureColourI { + private static final String ABSOLUTE = "abso"; + + private static final String ABOVE = "above"; + + private static final String BELOW = "below"; + + /* + * constants used to read or write a Jalview Features file + */ + private static final String LABEL = "label"; + + private static final String SCORE = "score"; + + private static final String ATTRIBUTE = "attribute"; + + private static final String NO_VALUE_MIN = "noValueMin"; + + private static final String NO_VALUE_MAX = "noValueMax"; + + private static final String NO_VALUE_NONE = "noValueNone"; + + static final Color DEFAULT_NO_COLOUR = null; + private static final String BAR = "|"; final private Color colour; @@ -41,10 +81,30 @@ public class FeatureColour implements FeatureColourI final private Color maxColour; + /* + * colour to use for colour by attribute when the + * attribute value is absent + */ + final private Color noColour; + + /* + * if true, then colour has a gradient based on a numerical + * range (either feature score, or an attribute value) + */ private boolean graduatedColour; + /* + * if true, colour values are generated from a text string, + * either feature description, or an attribute value + */ private boolean colourByLabel; + /* + * if not null, the value of [attribute, [sub-attribute] ...] + * is used for colourByLabel or graduatedColour + */ + private String[] attributeName; + private float threshold; private float base; @@ -55,8 +115,6 @@ public class FeatureColour implements FeatureColourI private boolean aboveThreshold; - private boolean thresholdIsMinOrMax; - private boolean isHighToLow; private boolean autoScaled; @@ -75,16 +133,29 @@ public class FeatureColour implements FeatureColourI /** * Parses a Jalview features file format colour descriptor - * [label|][mincolour|maxcolour - * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples: + *

            + * + * [label|score|[attribute|attributeName]|][mincolour|maxcolour| + * [absolute|]minvalue|maxvalue|[noValueOption|]thresholdtype|thresholdvalue] + *

            + * 'Score' is optional (default) for a graduated colour. An attribute with + * sub-attribute should be written as (for example) CSQ:Consequence. + * noValueOption is one of noValueMin, noValueMax, noValueNone + * with default noValueMin. + *

            + * Examples: *

              *
            • red
            • *
            • a28bbb
            • *
            • 25,125,213
            • *
            • label
            • + *
            • attribute|CSQ:PolyPhen
            • *
            • label|||0.0|0.0|above|12.5
            • *
            • label|||0.0|0.0|below|12.5
            • *
            • red|green|12.0|26.0|none
            • + *
            • score|red|green|12.0|26.0|none
            • + *
            • attribute|AF|red|green|12.0|26.0|none
            • + *
            • attribute|AF|red|green|noValueNone|12.0|26.0|none
            • *
            • a28bbb|3eb555|12.0|26.0|above|12.5
            • *
            • a28bbb|3eb555|abso|12.0|26.0|below|12.5
            • *
            @@ -94,34 +165,71 @@ public class FeatureColour implements FeatureColourI * @throws IllegalArgumentException * if not parseable */ - public static FeatureColour parseJalviewFeatureColour(String descriptor) + public static FeatureColourI parseJalviewFeatureColour(String descriptor) { - StringTokenizer gcol = new StringTokenizer(descriptor, "|", true); + StringTokenizer gcol = new StringTokenizer(descriptor, BAR, true); float min = Float.MIN_VALUE; float max = Float.MAX_VALUE; - boolean labelColour = false; + boolean byLabel = false; + boolean byAttribute = false; + String attName = null; + String mincol = null; + String maxcol = null; - String mincol = gcol.nextToken(); - if (mincol == "|") + /* + * first token should be 'label', or 'score', or an + * attribute name, or simple colour, or minimum colour + */ + String nextToken = gcol.nextToken(); + if (nextToken == BAR) { throw new IllegalArgumentException( "Expected either 'label' or a colour specification in the line: " + descriptor); } - String maxcol = null; - if (mincol.toLowerCase().indexOf("label") == 0) + if (nextToken.toLowerCase().startsWith(LABEL)) + { + byLabel = true; + // get the token after the next delimiter: + mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); + mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); + } + else if (nextToken.toLowerCase().startsWith(SCORE)) + { + mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); + mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); + } + else if (nextToken.toLowerCase().startsWith(ATTRIBUTE)) { - labelColour = true; + byAttribute = true; + attName = (gcol.hasMoreTokens() ? gcol.nextToken() : null); + attName = (gcol.hasMoreTokens() ? gcol.nextToken() : null); mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); - // skip '|' mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); } + else + { + mincol = nextToken; + } - if (!labelColour && !gcol.hasMoreTokens()) + /* + * if only one token, it can validly be label, attributeName, + * or a plain colour value + */ + if (!gcol.hasMoreTokens()) { - /* - * only a simple colour specification - parse it - */ + if (byLabel || byAttribute) + { + FeatureColourI fc = new FeatureColour(); + fc.setColourByLabel(true); + if (byAttribute) + { + fc.setAttributeName( + FeatureMatcher.fromAttributeDisplayName(attName)); + } + return fc; + } + Color colour = ColorUtils.parseColourString(descriptor); if (colour == null) { @@ -132,34 +240,64 @@ public class FeatureColour implements FeatureColourI } /* + * continue parsing for min/max/no colour (if graduated) + * and for threshold (colour by text or graduated) + */ + + /* * autoScaled == true: colours range over actual score range * autoScaled == false ('abso'): colours range over min/max range */ boolean autoScaled = true; String tok = null, minval, maxval; + String noValueColour = NO_VALUE_MIN; + if (mincol != null) { // at least four more tokens - if (mincol.equals("|")) + if (mincol.equals(BAR)) { - mincol = ""; + mincol = null; } else { gcol.nextToken(); // skip next '|' } maxcol = gcol.nextToken(); - if (maxcol.equals("|")) + if (maxcol.equals(BAR)) { - maxcol = ""; + maxcol = null; } else { gcol.nextToken(); // skip next '|' } tok = gcol.nextToken(); + + /* + * check for specifier for colour for no attribute value + * (new in 2.11, defaults to minColour if not specified) + */ + if (tok.equalsIgnoreCase(NO_VALUE_MIN)) + { + tok = gcol.nextToken(); + tok = gcol.nextToken(); + } + else if (tok.equalsIgnoreCase(NO_VALUE_MAX)) + { + noValueColour = NO_VALUE_MAX; + tok = gcol.nextToken(); + tok = gcol.nextToken(); + } + else if (tok.equalsIgnoreCase(NO_VALUE_NONE)) + { + noValueColour = NO_VALUE_NONE; + tok = gcol.nextToken(); + tok = gcol.nextToken(); + } + gcol.nextToken(); // skip next '|' - if (tok.toLowerCase().startsWith("abso")) + if (tok.toLowerCase().startsWith(ABSOLUTE)) { minval = gcol.nextToken(); gcol.nextToken(); // skip next '|' @@ -183,8 +321,8 @@ public class FeatureColour implements FeatureColourI } catch (Exception e) { throw new IllegalArgumentException( - "Couldn't parse the minimum value for graduated colour (" - + descriptor + ")"); + "Couldn't parse the minimum value for graduated colour ('" + + minval + "')"); } try { @@ -201,34 +339,45 @@ public class FeatureColour implements FeatureColourI } else { - // add in some dummy min/max colours for the label-only - // colourscheme. - mincol = "FFFFFF"; - maxcol = "000000"; + /* + * dummy min/max colours for colour by text + * (label or attribute value) + */ + mincol = "white"; + maxcol = "black"; + byLabel = true; } /* - * construct the FeatureColour + * construct the FeatureColour! */ FeatureColour featureColour; try { Color minColour = ColorUtils.parseColourString(mincol); Color maxColour = ColorUtils.parseColourString(maxcol); - featureColour = new FeatureColour(minColour, maxColour, min, max); - featureColour.setColourByLabel(labelColour); + Color noColour = noValueColour.equals(NO_VALUE_MAX) ? maxColour + : (noValueColour.equals(NO_VALUE_NONE) ? null : minColour); + featureColour = new FeatureColour(minColour, maxColour, noColour, min, + max); + featureColour.setColourByLabel(minColour == null); featureColour.setAutoScaled(autoScaled); + if (byAttribute) + { + featureColour.setAttributeName( + FeatureMatcher.fromAttributeDisplayName(attName)); + } // add in any additional parameters String ttype = null, tval = null; if (gcol.hasMoreTokens()) { // threshold type and possibly a threshold value ttype = gcol.nextToken(); - if (ttype.toLowerCase().startsWith("below")) + if (ttype.toLowerCase().startsWith(BELOW)) { featureColour.setBelowThreshold(true); } - else if (ttype.toLowerCase().startsWith("above")) + else if (ttype.toLowerCase().startsWith(ABOVE)) { featureColour.setAboveThreshold(true); } @@ -260,7 +409,7 @@ public class FeatureColour implements FeatureColourI "Ignoring additional tokens in parameters in graduated colour specification\n"); while (gcol.hasMoreTokens()) { - System.err.println("|" + gcol.nextToken()); + System.err.println(BAR + gcol.nextToken()); } System.err.println("\n"); } @@ -288,6 +437,7 @@ public class FeatureColour implements FeatureColourI { minColour = Color.WHITE; maxColour = Color.BLACK; + noColour = DEFAULT_NO_COLOUR; minRed = 0f; minGreen = 0f; minBlue = 0f; @@ -298,7 +448,8 @@ public class FeatureColour implements FeatureColourI } /** - * Constructor given a colour range and a score range + * Constructor given a colour range and a score range, defaulting 'no value + * colour' to be the same as minimum colour * * @param low * @param high @@ -307,36 +458,7 @@ public class FeatureColour implements FeatureColourI */ public FeatureColour(Color low, Color high, float min, float max) { - if (low == null) - { - low = Color.white; - } - if (high == null) - { - high = Color.black; - } - graduatedColour = true; - colour = null; - minColour = low; - maxColour = high; - threshold = Float.NaN; - isHighToLow = min >= max; - minRed = low.getRed() / 255f; - minGreen = low.getGreen() / 255f; - minBlue = low.getBlue() / 255f; - deltaRed = (high.getRed() / 255f) - minRed; - deltaGreen = (high.getGreen() / 255f) - minGreen; - deltaBlue = (high.getBlue() / 255f) - minBlue; - if (isHighToLow) - { - base = max; - range = min - max; - } - else - { - base = min; - range = max - min; - } + this(low, high, low, min, max); } /** @@ -350,6 +472,7 @@ public class FeatureColour implements FeatureColourI colour = fc.colour; minColour = fc.minColour; maxColour = fc.maxColour; + noColour = fc.noColour; minRed = fc.minRed; minGreen = fc.minGreen; minBlue = fc.minBlue; @@ -359,6 +482,7 @@ public class FeatureColour implements FeatureColourI base = fc.base; range = fc.range; isHighToLow = fc.isHighToLow; + attributeName = fc.attributeName; setAboveThreshold(fc.isAboveThreshold()); setBelowThreshold(fc.isBelowThreshold()); setThreshold(fc.getThreshold()); @@ -376,10 +500,54 @@ public class FeatureColour implements FeatureColourI public FeatureColour(FeatureColour fc, float min, float max) { this(fc); - graduatedColour = true; updateBounds(min, max); } + /** + * Constructor for a graduated colour + * + * @param low + * @param high + * @param noValueColour + * @param min + * @param max + */ + public FeatureColour(Color low, Color high, Color noValueColour, + float min, float max) + { + if (low == null) + { + low = Color.white; + } + if (high == null) + { + high = Color.black; + } + graduatedColour = true; + colour = null; + minColour = low; + maxColour = high; + noColour = noValueColour; + threshold = Float.NaN; + isHighToLow = min >= max; + minRed = low.getRed() / 255f; + minGreen = low.getGreen() / 255f; + minBlue = low.getBlue() / 255f; + deltaRed = (high.getRed() / 255f) - minRed; + deltaGreen = (high.getGreen() / 255f) - minGreen; + deltaBlue = (high.getBlue() / 255f) - minBlue; + if (isHighToLow) + { + base = max; + range = min - max; + } + else + { + base = min; + range = max - min; + } + } + @Override public boolean isGraduatedColour() { @@ -418,6 +586,12 @@ public class FeatureColour implements FeatureColourI } @Override + public Color getNoColour() + { + return noColour; + } + + @Override public boolean isColourByLabel() { return colourByLabel; @@ -470,18 +644,6 @@ public class FeatureColour implements FeatureColourI } @Override - public boolean isThresholdMinMax() - { - return thresholdIsMinOrMax; - } - - @Override - public void setThresholdMinMax(boolean b) - { - thresholdIsMinOrMax = b; - } - - @Override public float getThreshold() { return threshold; @@ -506,10 +668,7 @@ public class FeatureColour implements FeatureColourI } /** - * Updates the base and range appropriately for the given minmax range - * - * @param min - * @param max + * {@inheritDoc} */ @Override public void updateBounds(float min, float max) @@ -542,7 +701,10 @@ public class FeatureColour implements FeatureColourI { if (isColourByLabel()) { - return ColorUtils.createColourFromName(feature.getDescription()); + String label = attributeName == null ? feature.getDescription() + : feature.getValueAsString(attributeName); + return label == null ? noColour : ColorUtils + .createColourFromName(label); } if (!isGraduatedColour()) @@ -552,17 +714,31 @@ public class FeatureColour implements FeatureColourI /* * graduated colour case, optionally with threshold - * Float.NaN is assigned minimum visible score colour + * may be based on feature score on an attribute value + * Float.NaN, or no value, is assigned the 'no value' colour */ float scr = feature.getScore(); + if (attributeName != null) + { + try + { + String attVal = feature.getValueAsString(attributeName); + scr = Float.valueOf(attVal); + } catch (Throwable e) + { + scr = Float.NaN; + } + } if (Float.isNaN(scr)) { - return getMinColour(); + return noColour; } + if (isAboveThreshold() && scr <= threshold) { return null; } + if (isBelowThreshold() && scr >= threshold) { return null; @@ -635,21 +811,43 @@ public class FeatureColour implements FeatureColourI else { StringBuilder sb = new StringBuilder(32); - if (isColourByLabel()) + if (isColourByAttribute()) { - sb.append("label"); - if (hasThreshold()) - { - sb.append(BAR).append(BAR).append(BAR); - } + sb.append(ATTRIBUTE).append(BAR); + sb.append( + FeatureMatcher.toAttributeDisplayName(getAttributeName())); + } + else if (isColourByLabel()) + { + sb.append(LABEL); + } + else + { + sb.append(SCORE); } if (isGraduatedColour()) { - sb.append(Format.getHexString(getMinColour())).append(BAR); + sb.append(BAR).append(Format.getHexString(getMinColour())) + .append(BAR); sb.append(Format.getHexString(getMaxColour())).append(BAR); + String noValue = minColour.equals(noColour) ? NO_VALUE_MIN + : (maxColour.equals(noColour) ? NO_VALUE_MAX + : NO_VALUE_NONE); + sb.append(noValue).append(BAR); if (!isAutoScaled()) { - sb.append("abso").append(BAR); + sb.append(ABSOLUTE).append(BAR); + } + } + else + { + /* + * colour by text with score threshold: empty fields for + * minColour and maxColour (not used) + */ + if (hasThreshold()) + { + sb.append(BAR).append(BAR).append(BAR); } } if (hasThreshold() || isGraduatedColour()) @@ -658,11 +856,11 @@ public class FeatureColour implements FeatureColourI sb.append(getMax()).append(BAR); if (isBelowThreshold()) { - sb.append("below").append(BAR).append(getThreshold()); + sb.append(BELOW).append(BAR).append(getThreshold()); } else if (isAboveThreshold()) { - sb.append("above").append(BAR).append(getThreshold()); + sb.append(ABOVE).append(BAR).append(getThreshold()); } else { @@ -674,4 +872,22 @@ public class FeatureColour implements FeatureColourI return String.format("%s\t%s", featureType, colourString); } + @Override + public boolean isColourByAttribute() + { + return attributeName != null; + } + + @Override + public String[] getAttributeName() + { + return attributeName; + } + + @Override + public void setAttributeName(String... name) + { + attributeName = name; + } + } diff --git a/src/jalview/schemes/ResidueProperties.java b/src/jalview/schemes/ResidueProperties.java index 55df1d1..a4e6480 100755 --- a/src/jalview/schemes/ResidueProperties.java +++ b/src/jalview/schemes/ResidueProperties.java @@ -39,14 +39,14 @@ public class ResidueProperties public static final int[] purinepyrimidineIndex; - public static final Map aa3Hash = new HashMap(); + public static final Map aa3Hash = new HashMap<>(); - public static final Map aa2Triplet = new HashMap(); + public static final Map aa2Triplet = new HashMap<>(); - public static final Map nucleotideName = new HashMap(); + public static final Map nucleotideName = new HashMap<>(); // lookup from modified amino acid (e.g. MSE) to canonical form (e.g. MET) - public static final Map modifications = new HashMap(); + public static final Map modifications = new HashMap<>(); static { @@ -496,25 +496,27 @@ public class ResidueProperties * Color.white, // R Color.white, // Y Color.white, // N Color.white, // Gap */ - public static List STOP = Arrays.asList("TGA", "TAA", "TAG"); + public static String STOP = "STOP"; + + public static List STOP_CODONS = Arrays.asList("TGA", "TAA", "TAG"); public static String START = "ATG"; /** * Nucleotide Ambiguity Codes */ - public static final Map ambiguityCodes = new Hashtable(); + public static final Map ambiguityCodes = new Hashtable<>(); /** * Codon triplets with additional symbols for unambiguous codons that include * ambiguity codes */ - public static final Hashtable codonHash2 = new Hashtable(); + public static final Hashtable codonHash2 = new Hashtable<>(); /** * all ambiguity codes for a given base */ - public final static Hashtable> _ambiguityCodes = new Hashtable>(); + public final static Hashtable> _ambiguityCodes = new Hashtable<>(); static { @@ -638,7 +640,7 @@ public class ResidueProperties List codesfor = _ambiguityCodes.get(r); if (codesfor == null) { - _ambiguityCodes.put(r, codesfor = new ArrayList()); + _ambiguityCodes.put(r, codesfor = new ArrayList<>()); } if (!codesfor.contains(acode.getKey())) { @@ -755,27 +757,27 @@ public class ResidueProperties } // Stores residue codes/names and colours and other things - public static Map> propHash = new Hashtable>(); + public static Map> propHash = new Hashtable<>(); - public static Map hydrophobic = new Hashtable(); + public static Map hydrophobic = new Hashtable<>(); - public static Map polar = new Hashtable(); + public static Map polar = new Hashtable<>(); - public static Map small = new Hashtable(); + public static Map small = new Hashtable<>(); - public static Map positive = new Hashtable(); + public static Map positive = new Hashtable<>(); - public static Map negative = new Hashtable(); + public static Map negative = new Hashtable<>(); - public static Map charged = new Hashtable(); + public static Map charged = new Hashtable<>(); - public static Map aromatic = new Hashtable(); + public static Map aromatic = new Hashtable<>(); - public static Map aliphatic = new Hashtable(); + public static Map aliphatic = new Hashtable<>(); - public static Map tiny = new Hashtable(); + public static Map tiny = new Hashtable<>(); - public static Map proline = new Hashtable(); + public static Map proline = new Hashtable<>(); static { @@ -1149,7 +1151,7 @@ public class ResidueProperties String cdn = codonHash2.get(lccodon.toUpperCase()); if ("*".equals(cdn)) { - return "STOP"; + return STOP; } return cdn; } @@ -1157,7 +1159,7 @@ public class ResidueProperties public static Hashtable toDssp3State; static { - toDssp3State = new Hashtable(); + toDssp3State = new Hashtable<>(); toDssp3State.put("H", "H"); toDssp3State.put("E", "E"); toDssp3State.put("C", " "); @@ -2525,7 +2527,7 @@ public class ResidueProperties // / cut here public static void main(String[] args) { - Hashtable> aaProps = new Hashtable>(); + Hashtable> aaProps = new Hashtable<>(); System.out.println("my %aa = {"); // invert property hashes for (String pname : propHash.keySet()) @@ -2536,7 +2538,7 @@ public class ResidueProperties Vector aprops = aaProps.get(rname); if (aprops == null) { - aprops = new Vector(); + aprops = new Vector<>(); aaProps.put(rname, aprops); } Integer hasprop = phash.get(rname); @@ -2578,7 +2580,7 @@ public class ResidueProperties public static List getResidues(boolean forNucleotide, boolean includeAmbiguous) { - List result = new ArrayList(); + List result = new ArrayList<>(); if (forNucleotide) { for (String nuc : nucleotideName.keySet()) diff --git a/src/jalview/structure/StructureMapping.java b/src/jalview/structure/StructureMapping.java index 40789ed..4174f5b 100644 --- a/src/jalview/structure/StructureMapping.java +++ b/src/jalview/structure/StructureMapping.java @@ -21,6 +21,7 @@ package jalview.structure; import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.Mapping; import jalview.datamodel.SequenceI; import java.util.ArrayList; @@ -29,6 +30,12 @@ import java.util.List; public class StructureMapping { + public static final int UNASSIGNED_VALUE = Integer.MIN_VALUE; + + private static final int PDB_RES_NUM_INDEX = 0; + + private static final int PDB_ATOM_NUM_INDEX = 1; + String mappingDetails; SequenceI sequence; @@ -39,16 +46,12 @@ public class StructureMapping String pdbchain; - public static final int UNASSIGNED_VALUE = -1; - - private static final int PDB_RES_NUM_INDEX = 0; - - private static final int PDB_ATOM_NUM_INDEX = 1; - // Mapping key is residue index while value is an array containing PDB resNum, // and atomNo HashMap mapping; + jalview.datamodel.Mapping seqToPdbMapping = null; + /** * Constructor * @@ -73,6 +76,14 @@ public class StructureMapping this.mappingDetails = mappingDetails; } + public StructureMapping(SequenceI seq, String pdbFile2, String pdbId2, + String chain, HashMap mapping2, + String mappingOutput, Mapping seqToPdbMapping) + { + this(seq, pdbFile2, pdbId2, chain, mapping2, mappingOutput); + this.seqToPdbMapping = seqToPdbMapping; + } + public SequenceI getSequence() { return sequence; @@ -109,7 +120,8 @@ public class StructureMapping /** * * @param seqpos - * @return 0 or the corresponding residue number for the sequence position + * @return UNASSIGNED_VALUE or the corresponding residue number for the + * sequence position */ public int getPDBResNum(int seqpos) { @@ -134,7 +146,7 @@ public class StructureMapping */ public List getPDBResNumRanges(int fromSeqPos, int toSeqPos) { - List result = new ArrayList(); + List result = new ArrayList<>(); int startRes = -1; int endRes = -1; @@ -247,4 +259,110 @@ public class StructureMapping { return mapping; } + + public Mapping getSeqToPdbMapping() + { + return seqToPdbMapping; + } + + /** + * A hash function that satisfies the contract that if two mappings are + * equal(), they have the same hashCode + */ + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + + ((mappingDetails == null) ? 0 : mappingDetails.hashCode()); + result = prime * result + + ((pdbchain == null) ? 0 : pdbchain.hashCode()); + result = prime * result + ((pdbfile == null) ? 0 : pdbfile.hashCode()); + result = prime * result + ((pdbid == null) ? 0 : pdbid.hashCode()); + result = prime * result + + ((seqToPdbMapping == null) ? 0 : seqToPdbMapping.hashCode()); + result = prime * result + + ((sequence == null) ? 0 : sequence.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + StructureMapping other = (StructureMapping) obj; + if (mappingDetails == null) + { + if (other.mappingDetails != null) + { + return false; + } + } + else if (!mappingDetails.equals(other.mappingDetails)) + { + return false; + } + if (pdbchain == null) + { + if (other.pdbchain != null) + { + return false; + } + } + else if (!pdbchain.equals(other.pdbchain)) + { + return false; + } + if (pdbfile == null) + { + if (other.pdbfile != null) + { + return false; + } + } + else if (!pdbfile.equals(other.pdbfile)) + { + return false; + } + if (pdbid == null) + { + if (other.pdbid != null) + { + return false; + } + } + else if (!pdbid.equals(other.pdbid)) + { + return false; + } + if (seqToPdbMapping == null) + { + if (other.seqToPdbMapping != null) + { + return false; + } + } + else if (!seqToPdbMapping.equals(other.seqToPdbMapping)) + { + return false; + } + if (sequence != other.sequence) + { + return false; + } + + return true; + } } diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index 35e2536..cd986c0 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -74,8 +74,6 @@ public class StructureSelectionManager private boolean addTempFacAnnot = false; - private SiftsClient siftsClient = null; - /* * Set of any registered mappings between (dataset) sequences. */ @@ -287,7 +285,8 @@ public class StructureSelectionManager } /** - * Returns the file name for a mapped PDB id (or null if not mapped). + * Returns the filename the PDB id is already mapped to if known, or null if + * it is not mapped * * @param pdbid * @return @@ -296,7 +295,7 @@ public class StructureSelectionManager { for (StructureMapping sm : mappings) { - if (sm.getPdbId().equals(pdbid)) + if (sm.getPdbId().equalsIgnoreCase(pdbid)) { return sm.pdbfile; } @@ -328,22 +327,20 @@ public class StructureSelectionManager } /** - * create sequence structure mappings between each sequence and the given - * pdbFile (retrieved via the given protocol). + * Import a single structure file and register sequence structure mappings for + * broadcasting colouring, mouseovers and selection events (convenience + * wrapper). * * @param forStructureView * when true, record the mapping for use in mouseOvers - * - * @param sequenceArray + * @param sequence * - one or more sequences to be mapped to pdbFile - * @param targetChainIds + * @param targetChains * - optional chain specification for mapping each sequence to pdb - * (may be nill, individual elements may be nill) - JBPNote: JAL-2693 - * - this should be List>, empty lists indicate no - * predefined mappings + * (may be nill, individual elements may be nill) * @param pdbFile * - structure data resource - * @param sourceType + * @param protocol * - how to resolve data from resource * @return null or the structure data parsed as a pdb file */ @@ -355,49 +352,57 @@ public class StructureSelectionManager pdbFile, sourceType, null); } + /** + * create sequence structure mappings between each sequence and the given + * pdbFile (retrieved via the given protocol). Either constructs a mapping + * using NW alignment or derives one from any available SIFTS mapping data. + * + * @param forStructureView + * when true, record the mapping for use in mouseOvers + * + * @param sequenceArray + * - one or more sequences to be mapped to pdbFile + * @param targetChainIds + * - optional chain specification for mapping each sequence to pdb + * (may be nill, individual elements may be nill) - JBPNote: JAL-2693 + * - this should be List>, empty lists indicate no + * predefined mappings + * @param pdbFile + * - structure data resource + * @param sourceType + * - how to resolve data from resource + * @param IProgressIndicator + * reference to UI component that maintains a progress bar for the + * mapping operation + * @return null or the structure data parsed as a pdb file + */ synchronized public StructureFile computeMapping( boolean forStructureView, SequenceI[] sequenceArray, String[] targetChainIds, String pdbFile, DataSourceType sourceType, IProgressIndicator progress) { long progressSessionId = System.currentTimeMillis() * 3; - /* - * There will be better ways of doing this in the future, for now we'll use - * the tried and tested MCview pdb mapping + + /** + * do we extract and transfer annotation from 3D data ? */ - boolean parseSecStr = processSecondaryStructure; - if (isPDBFileRegistered(pdbFile)) - { - for (SequenceI sq : sequenceArray) - { - SequenceI ds = sq; - while (ds.getDatasetSequence() != null) - { - ds = ds.getDatasetSequence(); - } - ; - if (ds.getAnnotation() != null) - { - for (AlignmentAnnotation ala : ds.getAnnotation()) - { - // false if any annotation present from this structure - // JBPNote this fails for jmol/chimera view because the *file* is - // passed, not the structure data ID - - if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile))) - { - parseSecStr = false; - } - } - } - } - } + // FIXME: possibly should just delete + + boolean parseSecStr = processSecondaryStructure + ? isStructureFileProcessed(pdbFile, sequenceArray) + : false; + StructureFile pdb = null; boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts(); try { + // FIXME if sourceType is not null, we've lost data here sourceType = AppletFormatAdapter.checkProtocol(pdbFile); - pdb = new JmolParser(pdbFile, sourceType); - + pdb = new JmolParser(false, pdbFile, sourceType); + pdb.addSettings(parseSecStr && processSecondaryStructure, + parseSecStr && addTempFacAnnot, + parseSecStr && secStructServices); + pdb.doParse(); if (pdb.getId() != null && pdb.getId().trim().length() > 0 && DataSourceType.FILE == sourceType) { @@ -411,7 +416,10 @@ public class StructureSelectionManager ex.printStackTrace(); return null; } - + /* + * sifts client - non null if SIFTS mappings are to be used + */ + SiftsClient siftsClient = null; try { if (isMapUsingSIFTs) @@ -422,6 +430,7 @@ public class StructureSelectionManager { isMapUsingSIFTs = false; e.printStackTrace(); + siftsClient = null; } String targetChainId; @@ -524,12 +533,12 @@ public class StructureSelectionManager try { siftsMapping = getStructureMapping(seq, pdbFile, targetChainId, - pdb, maxChain, sqmpping, maxAlignseq); + pdb, maxChain, sqmpping, maxAlignseq, siftsClient); seqToStrucMapping.add(siftsMapping); - maxChain.makeExactMapping(maxAlignseq, seq); - maxChain.transferRESNUMFeatures(seq, null);// FIXME: is this + maxChain.makeExactMapping(siftsMapping, seq); + maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS");// FIXME: is this // "IEA:SIFTS" ? - maxChain.transferResidueAnnotation(siftsMapping, sqmpping); + maxChain.transferResidueAnnotation(siftsMapping, null); ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0)); } catch (SiftsException e) @@ -540,7 +549,8 @@ public class StructureSelectionManager targetChainId, maxChain, pdb, maxAlignseq); seqToStrucMapping.add(nwMapping); maxChain.makeExactMapping(maxAlignseq, seq); - maxChain.transferRESNUMFeatures(seq, null); // FIXME: is this + maxChain.transferRESNUMFeatures(seq, "IEA:Jalview"); // FIXME: is + // this // "IEA:Jalview" ? maxChain.transferResidueAnnotation(nwMapping, sqmpping); ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0)); @@ -551,24 +561,32 @@ public class StructureSelectionManager List foundSiftsMappings = new ArrayList<>(); for (PDBChain chain : pdb.getChains()) { + StructureMapping siftsMapping = null; try { - StructureMapping siftsMapping = getStructureMapping(seq, - pdbFile, chain.id, pdb, chain, sqmpping, maxAlignseq); + siftsMapping = getStructureMapping(seq, + pdbFile, chain.id, pdb, chain, sqmpping, maxAlignseq, + siftsClient); foundSiftsMappings.add(siftsMapping); + chain.makeExactMapping(siftsMapping, seq); + chain.transferRESNUMFeatures(seq, "IEA: SIFTS");// FIXME: is this + // "IEA:SIFTS" ? + chain.transferResidueAnnotation(siftsMapping, null); } catch (SiftsException e) { System.err.println(e.getMessage()); } + catch (Exception e) + { + System.err + .println( + "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair"); + System.err.println(e.getMessage()); + } } if (!foundSiftsMappings.isEmpty()) { seqToStrucMapping.addAll(foundSiftsMappings); - maxChain.makeExactMapping(maxAlignseq, seq); - maxChain.transferRESNUMFeatures(seq, null);// FIXME: is this - // "IEA:SIFTS" ? - maxChain.transferResidueAnnotation(foundSiftsMappings.get(0), - sqmpping); ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0)); } else @@ -598,7 +616,10 @@ public class StructureSelectionManager } if (forStructureView) { - mappings.addAll(seqToStrucMapping); + for (StructureMapping sm : seqToStrucMapping) + { + addStructureMapping(sm); // not addAll! + } } if (progress != null) { @@ -608,9 +629,52 @@ public class StructureSelectionManager return pdb; } + /** + * check if we need to extract secondary structure from given pdbFile and + * transfer to sequences + * + * @param pdbFile + * @param sequenceArray + * @return + */ + private boolean isStructureFileProcessed(String pdbFile, + SequenceI[] sequenceArray) + { + boolean parseSecStr = true; + if (isPDBFileRegistered(pdbFile)) + { + for (SequenceI sq : sequenceArray) + { + SequenceI ds = sq; + while (ds.getDatasetSequence() != null) + { + ds = ds.getDatasetSequence(); + } + ; + if (ds.getAnnotation() != null) + { + for (AlignmentAnnotation ala : ds.getAnnotation()) + { + // false if any annotation present from this structure + // JBPNote this fails for jmol/chimera view because the *file* is + // passed, not the structure data ID - + if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile))) + { + parseSecStr = false; + } + } + } + } + } + return parseSecStr; + } + public void addStructureMapping(StructureMapping sm) { - mappings.add(sm); + if (!mappings.contains(sm)) + { + mappings.add(sm); + } } /** @@ -624,13 +688,15 @@ public class StructureSelectionManager * @param maxChain * @param sqmpping * @param maxAlignseq + * @param siftsClient + * client for retrieval of SIFTS mappings for this structure * @return * @throws SiftsException */ private StructureMapping getStructureMapping(SequenceI seq, String pdbFile, String targetChainId, StructureFile pdb, PDBChain maxChain, jalview.datamodel.Mapping sqmpping, - AlignSeq maxAlignseq) throws SiftsException + AlignSeq maxAlignseq, SiftsClient siftsClient) throws SiftsException { StructureMapping curChainMapping = siftsClient .getSiftsStructureMapping(seq, pdbFile, targetChainId); @@ -639,7 +705,7 @@ public class StructureSelectionManager PDBChain chain = pdb.findChain(targetChainId); if (chain != null) { - chain.transferResidueAnnotation(curChainMapping, sqmpping); + chain.transferResidueAnnotation(curChainMapping, null); } } catch (Exception e) { diff --git a/src/jalview/util/ColorUtils.java b/src/jalview/util/ColorUtils.java index d4be322..60129fb 100644 --- a/src/jalview/util/ColorUtils.java +++ b/src/jalview/util/ColorUtils.java @@ -25,10 +25,17 @@ package jalview.util; import java.awt.Color; +import java.util.HashMap; +import java.util.Map; import java.util.Random; public class ColorUtils { + private static final int MAX_CACHE_SIZE = 1729; + /* + * a cache for colours generated from text strings + */ + static Map myColours = new HashMap<>(); /** * Generates a random color, will mix with input color. Code taken from @@ -260,6 +267,10 @@ public class ColorUtils { return Color.white; } + if (myColours.containsKey(name)) + { + return myColours.get(name); + } int lsize = name.length(); int start = 0; int end = lsize / 3; @@ -291,6 +302,11 @@ public class ColorUtils Color color = new Color(r, g, b); + if (myColours.size() < MAX_CACHE_SIZE) + { + myColours.put(name, color); + } + return color; } diff --git a/src/jalview/util/MapList.java b/src/jalview/util/MapList.java index 4658724..9f28ee1 100644 --- a/src/jalview/util/MapList.java +++ b/src/jalview/util/MapList.java @@ -77,8 +77,8 @@ public class MapList */ public MapList() { - fromShifts = new ArrayList(); - toShifts = new ArrayList(); + fromShifts = new ArrayList<>(); + toShifts = new ArrayList<>(); } /** @@ -116,8 +116,17 @@ public class MapList { int hashCode = 31 * fromRatio; hashCode = 31 * hashCode + toRatio; - hashCode = 31 * hashCode + fromShifts.toArray().hashCode(); - hashCode = 31 * hashCode + toShifts.toArray().hashCode(); + for (int[] shift : fromShifts) + { + hashCode = 31 * hashCode + shift[0]; + hashCode = 31 * hashCode + shift[1]; + } + for (int[] shift : toShifts) + { + hashCode = 31 * hashCode + shift[0]; + hashCode = 31 * hashCode + shift[1]; + } + return hashCode; } @@ -318,6 +327,13 @@ public class MapList fromHighest = Integer.MIN_VALUE; for (int[] range : fromRange) { + if (range.length != 2) + { + // throw new IllegalArgumentException(range); + System.err.println( + "Invalid format for fromRange " + Arrays.toString(range) + + " may cause errors"); + } fromLowest = Math.min(fromLowest, Math.min(range[0], range[1])); fromHighest = Math.max(fromHighest, Math.max(range[0], range[1])); } @@ -326,6 +342,13 @@ public class MapList toHighest = Integer.MIN_VALUE; for (int[] range : toRange) { + if (range.length != 2) + { + // throw new IllegalArgumentException(range); + System.err.println("Invalid format for toRange " + + Arrays.toString(range) + + " may cause errors"); + } toLowest = Math.min(toLowest, Math.min(range[0], range[1])); toHighest = Math.max(toHighest, Math.max(range[0], range[1])); } @@ -347,7 +370,7 @@ public class MapList } boolean changed = false; - List merged = new ArrayList(); + List merged = new ArrayList<>(); int[] lastRange = ranges.get(0); int lastDirection = lastRange[1] >= lastRange[0] ? 1 : -1; lastRange = new int[] { lastRange[0], lastRange[1] }; @@ -803,7 +826,7 @@ public class MapList { return null; } - List ranges = new ArrayList(); + List ranges = new ArrayList<>(); if (fs <= fe) { intv = fs; @@ -1094,8 +1117,33 @@ public class MapList */ public boolean isFromForwardStrand() { + return isForwardStrand(getFromRanges()); + } + + /** + * Returns true if mapping is to forward strand, false if to reverse strand. + * Result is just based on the first 'to' range that is not a single position. + * Default is true unless proven to be false. Behaviour is not well defined if + * the mapping has a mixture of forward and reverse ranges. + * + * @return + */ + public boolean isToForwardStrand() + { + return isForwardStrand(getToRanges()); + } + + /** + * A helper method that returns true unless at least one range has start > end. + * Behaviour is undefined for a mixture of forward and reverse ranges. + * + * @param ranges + * @return + */ + private boolean isForwardStrand(List ranges) + { boolean forwardStrand = true; - for (int[] range : getFromRanges()) + for (int[] range : ranges) { if (range[1] > range[0]) { @@ -1120,4 +1168,72 @@ public class MapList || (fromRatio == 3 && toRatio == 1); } + /** + * Returns a map which is the composite of this one and the input map. That + * is, the output map has the fromRanges of this map, and its toRanges are the + * toRanges of this map as transformed by the input map. + *

            + * Returns null if the mappings cannot be traversed (not all toRanges of this + * map correspond to fromRanges of the input), or if this.toRatio does not + * match map.fromRatio. + * + *

            +   * Example 1:
            +   *    this:   from [1-100] to [501-600]
            +   *    input:  from [10-40] to [60-90]
            +   *    output: from [10-40] to [560-590]
            +   * Example 2 ('reverse strand exons'):
            +   *    this:   from [1-100] to [2000-1951], [1000-951] // transcript to loci
            +   *    input:  from [1-50]  to [41-90] // CDS to transcript
            +   *    output: from [10-40] to [1960-1951], [1000-971] // CDS to gene loci
            +   * 
            + * + * @param map + * @return + */ + public MapList traverse(MapList map) + { + if (map == null) + { + return null; + } + + /* + * compound the ratios by this rule: + * A:B with M:N gives A*M:B*N + * reduced by greatest common divisor + * so 1:3 with 3:3 is 3:9 or 1:3 + * 1:3 with 3:1 is 3:3 or 1:1 + * 1:3 with 1:3 is 1:9 + * 2:5 with 3:7 is 6:35 + */ + int outFromRatio = getFromRatio() * map.getFromRatio(); + int outToRatio = getToRatio() * map.getToRatio(); + int gcd = MathUtils.gcd(outFromRatio, outToRatio); + outFromRatio /= gcd; + outToRatio /= gcd; + + List toRanges = new ArrayList<>(); + for (int[] range : getToRanges()) + { + int[] transferred = map.locateInTo(range[0], range[1]); + if (transferred == null || transferred.length % 2 != 0) + { + return null; + } + + /* + * convert [start1, end1, start2, end2, ...] + * to [[start1, end1], [start2, end2], ...] + */ + for (int i = 0; i < transferred.length;) + { + toRanges.add(new int[] { transferred[i], transferred[i + 1] }); + i += 2; + } + } + + return new MapList(getFromRanges(), toRanges, outFromRatio, outToRatio); + } + } diff --git a/src/jalview/util/MappingUtils.java b/src/jalview/util/MappingUtils.java index 9c5c109..b552c21 100644 --- a/src/jalview/util/MappingUtils.java +++ b/src/jalview/util/MappingUtils.java @@ -542,9 +542,11 @@ public final class MappingUtils toSequences, fromGapChar); } - for (int[] hidden : hiddencols.getHiddenColumnsCopy()) + Iterator regions = hiddencols.iterator(); + while (regions.hasNext()) { - mapHiddenColumns(hidden, codonFrames, newHidden, fromSequences, + mapHiddenColumns(regions.next(), codonFrames, newHidden, + fromSequences, toSequences, fromGapChar); } return; // mappedColumns; @@ -941,6 +943,34 @@ public final class MappingUtils } /** + * Answers true if range's start-end positions include those of queryRange, + * where either range might be in reverse direction, else false + * + * @param range + * a start-end range + * @param queryRange + * a candidate subrange of range (start2-end2) + * @return + */ + public static boolean rangeContains(int[] range, int[] queryRange) + { + if (range == null || queryRange == null || range.length != 2 + || queryRange.length != 2) + { + /* + * invalid arguments + */ + return false; + } + + int min = Math.min(range[0], range[1]); + int max = Math.max(range[0], range[1]); + + return (min <= queryRange[0] && max >= queryRange[0] + && min <= queryRange[1] && max >= queryRange[1]); + } + + /** * Removes the specified number of positions from the given ranges. Provided * to allow a stop codon to be stripped from a CDS sequence so that it matches * the peptide translation length. diff --git a/src/jalview/util/MathUtils.java b/src/jalview/util/MathUtils.java new file mode 100644 index 0000000..72d46a2 --- /dev/null +++ b/src/jalview/util/MathUtils.java @@ -0,0 +1,22 @@ +package jalview.util; + +public class MathUtils +{ + + /** + * Returns the greatest common divisor of two integers + * + * @param a + * @param b + * @return + */ + public static int gcd(int a, int b) + { + if (b == 0) + { + return Math.abs(a); + } + return gcd(b, a % b); + } + +} diff --git a/src/jalview/util/StringUtils.java b/src/jalview/util/StringUtils.java index b3456aa..2e8ace8 100644 --- a/src/jalview/util/StringUtils.java +++ b/src/jalview/util/StringUtils.java @@ -403,4 +403,45 @@ public class StringUtils } return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); } + + /** + * A helper method that strips off any leading or trailing html and body tags. + * If no html tag is found, then also html-encodes angle bracket characters. + * + * @param text + * @return + */ + public static String stripHtmlTags(String text) + { + if (text == null) + { + return null; + } + String tmp2up = text.toUpperCase(); + int startTag = tmp2up.indexOf(""); + if (startTag > -1) + { + text = text.substring(startTag + 6); + tmp2up = tmp2up.substring(startTag + 6); + } + // is omission of "" intentional here?? + int endTag = tmp2up.indexOf(""); + if (endTag > -1) + { + text = text.substring(0, endTag); + tmp2up = tmp2up.substring(0, endTag); + } + endTag = tmp2up.indexOf(""); + if (endTag > -1) + { + text = text.substring(0, endTag); + } + + if (startTag == -1 && (text.contains("<") || text.contains(">"))) + { + text = text.replaceAll("<", "<"); + text = text.replaceAll(">", ">"); + } + return text; + } } diff --git a/src/jalview/util/matcher/Condition.java b/src/jalview/util/matcher/Condition.java new file mode 100644 index 0000000..8816a7f --- /dev/null +++ b/src/jalview/util/matcher/Condition.java @@ -0,0 +1,102 @@ +package jalview.util.matcher; + +import jalview.util.MessageManager; + +/** + * An enumeration for binary conditions that a user might choose from when + * setting filter or match conditions for values + */ +public enum Condition +{ + Contains(false, true, "Contains"), + NotContains(false, true, "NotContains"), Matches(false, true, "Matches"), + NotMatches(false, true, "NotMatches"), Present(false, false, "Present"), + NotPresent(false, false, "NotPresent"), EQ(true, true, "EQ"), + NE(true, true, "NE"), LT(true, true, "LT"), LE(true, true, "LE"), + GT(true, true, "GT"), GE(true, true, "GE"); + + private boolean numeric; + + private boolean needsAPattern; + + /* + * value used to save a Condition to the + * Jalview project file or restore it from project; + * it should not be changed even if enum names change in future + */ + private String stableName; + + /** + * Answers the enum value whose 'stable name' matches the argument (not case + * sensitive), or null if no match + * + * @param stableName + * @return + */ + public static Condition fromString(String stableName) + { + for (Condition c : values()) + { + if (c.stableName.equalsIgnoreCase(stableName)) + { + return c; + } + } + return null; + } + + /** + * Constructor + * + * @param isNumeric + * @param needsPattern + * @param stablename + */ + Condition(boolean isNumeric, boolean needsPattern, String stablename) + { + numeric = isNumeric; + needsAPattern = needsPattern; + stableName = stablename; + } + + /** + * Answers true if the condition does a numerical comparison, else false + * (string comparison) + * + * @return + */ + public boolean isNumeric() + { + return numeric; + } + + /** + * Answers true if the condition requires a pattern to compare against, else + * false + * + * @return + */ + public boolean needsAPattern() + { + return needsAPattern; + } + + public String getStableName() + { + return stableName; + } + + /** + * Answers a display name for the match condition, suitable for showing in + * drop-down menus. The value may be internationalized using the resource key + * "label.matchCondition_" with the enum name appended. + * + * @return + */ + @Override + public String toString() + { + return MessageManager.getStringOrReturn("label.matchCondition_", + name()); + } +} diff --git a/src/jalview/util/matcher/Matcher.java b/src/jalview/util/matcher/Matcher.java new file mode 100644 index 0000000..0792509 --- /dev/null +++ b/src/jalview/util/matcher/Matcher.java @@ -0,0 +1,251 @@ +package jalview.util.matcher; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * A bean to describe one attribute-based filter + */ +public class Matcher implements MatcherI +{ + /* + * the comparison condition + */ + Condition condition; + + /* + * the string pattern as entered, or the regex, to compare to + * also holds the string form of float value if a numeric condition + */ + String pattern; + + /* + * the pattern in upper case, for non-case-sensitive matching + */ + String uppercasePattern; + + /* + * the compiled regex if using a pattern match condition + * (reserved for possible future enhancement) + */ + Pattern regexPattern; + + /* + * the value to compare to for a numerical condition + */ + float value; + + /** + * Constructor + * + * @param cond + * @param compareTo + * @return + * @throws NumberFormatException + * if a numerical condition is specified with a non-numeric + * comparison value + * @throws NullPointerException + * if a null condition or comparison string is specified + */ + public Matcher(Condition cond, String compareTo) + { + Objects.requireNonNull(cond); + condition = cond; + if (cond.isNumeric()) + { + value = Float.valueOf(compareTo); + pattern = String.valueOf(value); + uppercasePattern = pattern; + } + else + { + pattern = compareTo; + if (pattern != null) + { + uppercasePattern = pattern.toUpperCase(); + } + } + + // if we add regex conditions (e.g. matchesPattern), then + // pattern should hold the raw regex, and + // regexPattern = Pattern.compile(compareTo); + } + + /** + * Constructor for a numerical match condition. Note that if a string + * comparison condition is specified, this will be converted to a comparison + * with the float value as string + * + * @param cond + * @param compareTo + */ + public Matcher(Condition cond, float compareTo) + { + this(cond, String.valueOf(compareTo)); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("incomplete-switch") + @Override + public boolean matches(String val) + { + if (condition.isNumeric()) + { + try + { + /* + * treat a null value (no such attribute) as + * failing any numerical filter condition + */ + return val == null ? false : matches(Float.valueOf(val)); + } catch (NumberFormatException e) + { + return false; + } + } + + /* + * a null value matches a negative condition, fails a positive test + */ + if (val == null) + { + return condition == Condition.NotContains + || condition == Condition.NotMatches + || condition == Condition.NotPresent; + } + + String upper = val.toUpperCase().trim(); + boolean matched = false; + switch(condition) { + case Matches: + matched = upper.equals(uppercasePattern); + break; + case NotMatches: + matched = !upper.equals(uppercasePattern); + break; + case Contains: + matched = upper.indexOf(uppercasePattern) > -1; + break; + case NotContains: + matched = upper.indexOf(uppercasePattern) == -1; + break; + case Present: + matched = true; + break; + default: + break; + } + return matched; + } + + /** + * Applies a numerical comparison match condition + * + * @param f + * @return + */ + @SuppressWarnings("incomplete-switch") + boolean matches(float f) + { + if (!condition.isNumeric()) + { + return matches(String.valueOf(f)); + } + + boolean matched = false; + switch (condition) { + case LT: + matched = f < value; + break; + case LE: + matched = f <= value; + break; + case EQ: + matched = f == value; + break; + case NE: + matched = f != value; + break; + case GT: + matched = f > value; + break; + case GE: + matched = f >= value; + break; + default: + break; + } + + return matched; + } + + /** + * A simple hash function that guarantees that when two objects are equal, + * they have the same hashcode + */ + @Override + public int hashCode() + { + return pattern.hashCode() + condition.hashCode() + (int) value; + } + + /** + * equals is overridden so that we can safely remove Matcher objects from + * collections (e.g. delete an attribute match condition for a feature colour) + */ + @Override + public boolean equals(Object obj) + { + if (obj == null || !(obj instanceof Matcher)) + { + return false; + } + Matcher m = (Matcher) obj; + if (condition != m.condition || value != m.value) + { + return false; + } + if (pattern == null) + { + return m.pattern == null; + } + return uppercasePattern.equals(m.uppercasePattern); + } + + @Override + public Condition getCondition() + { + return condition; + } + + @Override + public String getPattern() + { + return pattern; + } + + @Override + public float getFloatValue() + { + return value; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(condition.toString()).append(" "); + if (condition.isNumeric()) + { + sb.append(pattern); + } + else + { + sb.append("'").append(pattern).append("'"); + } + + return sb.toString(); + } +} diff --git a/src/jalview/util/matcher/MatcherI.java b/src/jalview/util/matcher/MatcherI.java new file mode 100644 index 0000000..ca6d44c --- /dev/null +++ b/src/jalview/util/matcher/MatcherI.java @@ -0,0 +1,18 @@ +package jalview.util.matcher; + +public interface MatcherI +{ + /** + * Answers true if the given value is matched, else false + * + * @param s + * @return + */ + boolean matches(String s); + + Condition getCondition(); + + String getPattern(); + + float getFloatValue(); +} diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index a0cbff4..1366ada 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -67,6 +67,7 @@ import java.util.BitSet; import java.util.Deque; import java.util.HashMap; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -1740,8 +1741,12 @@ public abstract class AlignmentViewport if (alignment.getHiddenColumns() != null && alignment.getHiddenColumns().hasHiddenColumns()) { - selection = alignment.getHiddenColumns() - .getVisibleSequenceStrings(start, end, seqs); + for (i = 0; i < iSize; i++) + { + Iterator blocks = alignment.getHiddenColumns() + .getVisContigsIterator(start, end + 1, false); + selection[i] = seqs[i].getSequenceStringFromIterator(blocks); + } } else { @@ -1768,10 +1773,10 @@ public abstract class AlignmentViewport { if (start == 0) { - start = hidden.adjustForHiddenColumns(start); + start = hidden.visibleToAbsoluteColumn(start); } - end = hidden.getHiddenBoundaryRight(start); + end = hidden.getNextHiddenBoundary(false, start); if (start == end) { end = max; @@ -1786,8 +1791,8 @@ public abstract class AlignmentViewport if (hidden != null && hidden.hasHiddenColumns()) { - start = hidden.adjustForHiddenColumns(end); - start = hidden.getHiddenBoundaryLeft(start) + 1; + start = hidden.visibleToAbsoluteColumn(end); + start = hidden.getNextHiddenBoundary(true, start) + 1; } } while (end < max); @@ -1809,13 +1814,13 @@ public abstract class AlignmentViewport AlignmentAnnotation clone = new AlignmentAnnotation(annot); if (selectedOnly && selectionGroup != null) { - alignment.getHiddenColumns().makeVisibleAnnotation( + clone.makeVisibleAnnotation( selectionGroup.getStartRes(), selectionGroup.getEndRes(), - clone); + alignment.getHiddenColumns()); } else { - alignment.getHiddenColumns().makeVisibleAnnotation(clone); + clone.makeVisibleAnnotation(alignment.getHiddenColumns()); } ala.add(clone); } @@ -2781,7 +2786,7 @@ public abstract class AlignmentViewport int lastSeq = alignment.getHeight() - 1; List seqMappings = null; for (int seqNo = ranges - .getStartSeq(); seqNo < lastSeq; seqNo++, seqOffset++) + .getStartSeq(); seqNo <= lastSeq; seqNo++, seqOffset++) { sequence = getAlignment().getSequenceAt(seqNo); if (hiddenSequences != null && hiddenSequences.isHidden(sequence)) diff --git a/src/jalview/viewmodel/OverviewDimensions.java b/src/jalview/viewmodel/OverviewDimensions.java index 170f4e9..0235081 100644 --- a/src/jalview/viewmodel/OverviewDimensions.java +++ b/src/jalview/viewmodel/OverviewDimensions.java @@ -58,6 +58,10 @@ public abstract class OverviewDimensions protected int alheight; + protected float widthRatio; + + protected float heightRatio; + /** * Create an OverviewDimensions object * @@ -157,23 +161,25 @@ public abstract class OverviewDimensions public float getPixelsPerCol() { resetAlignmentDims(); - return (float) width / alwidth; + return 1 / widthRatio; } public float getPixelsPerSeq() { resetAlignmentDims(); - return (float) sequencesHeight / alheight; + return 1 / heightRatio; } public void setWidth(int w) { width = w; + widthRatio = (float) alwidth / width; } public void setHeight(int h) { sequencesHeight = h - graphHeight; + heightRatio = (float) alheight / sequencesHeight; } /** @@ -273,14 +279,14 @@ public abstract class OverviewDimensions // boxX, boxY is the x,y location equivalent to startRes, startSeq int xPos = Math.min(startRes, alwidth - vpwidth + 1); - boxX = Math.round((float) xPos * width / alwidth); - boxY = Math.round((float) startSeq * sequencesHeight / alheight); + boxX = Math.round(xPos / widthRatio); + boxY = Math.round(startSeq / heightRatio); // boxWidth is the width in residues translated to pixels - boxWidth = Math.round((float) vpwidth * width / alwidth); + boxWidth = Math.round(vpwidth / widthRatio); // boxHeight is the height in sequences translated to pixels - boxHeight = Math.round((float) vpheight * sequencesHeight / alheight); + boxHeight = Math.round(vpheight / heightRatio); } /** diff --git a/src/jalview/viewmodel/OverviewDimensionsHideHidden.java b/src/jalview/viewmodel/OverviewDimensionsHideHidden.java index c158ce7..de90a21 100644 --- a/src/jalview/viewmodel/OverviewDimensionsHideHidden.java +++ b/src/jalview/viewmodel/OverviewDimensionsHideHidden.java @@ -50,6 +50,8 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions public void updateViewportFromMouse(int mousex, int mousey, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { + resetAlignmentDims(); + int xAsRes = getLeftXFromCentreX(mousex, hiddenCols); int yAsSeq = getTopYFromCentreY(mousey, hiddenSeqs); @@ -61,24 +63,29 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions public void adjustViewportFromMouse(int mousex, int mousey, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { + resetAlignmentDims(); + // calculate translation in pixel terms: // get mouse location in viewport coords, add translation in viewport // coords, and update viewport as usual - int vpx = Math.round((float) mousex * alwidth / width); - int vpy = Math.round((float) mousey * alheight / sequencesHeight); + int vpx = Math.round(mousex * widthRatio); + int vpy = Math.round(mousey * heightRatio); updateViewportFromTopLeft(vpx + xdiff, vpy + ydiff, hiddenSeqs, hiddenCols); } + /** + * {@inheritDoc} Callers should have already called resetAlignmentDims to + * refresh alwidth, alheight and width/height ratios + */ @Override protected void updateViewportFromTopLeft(int leftx, int topy, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { int xAsRes = leftx; int yAsSeq = topy; - resetAlignmentDims(); if (xAsRes < 0) { @@ -147,7 +154,7 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions public AlignmentColsCollectionI getColumns(AlignmentI al) { return new VisibleColsCollection(0, - ranges.getAbsoluteAlignmentWidth() - 1, al); + ranges.getAbsoluteAlignmentWidth() - 1, al.getHiddenColumns()); } @Override @@ -162,19 +169,30 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions { alwidth = ranges.getVisibleAlignmentWidth(); alheight = ranges.getVisibleAlignmentHeight(); + + widthRatio = (float) alwidth / width; + heightRatio = (float) alheight / sequencesHeight; } + /** + * {@inheritDoc} Callers should have already called resetAlignmentDims to + * refresh widthRatio + */ @Override protected int getLeftXFromCentreX(int mousex, HiddenColumns hidden) { - int vpx = Math.round((float) mousex * alwidth / width); + int vpx = Math.round(mousex * widthRatio); return vpx - ranges.getViewportWidth() / 2; } + /** + * {@inheritDoc} Callers should have already called resetAlignmentDims to + * refresh heightRatio + */ @Override protected int getTopYFromCentreY(int mousey, HiddenSequences hidden) { - int vpy = Math.round((float) mousey * alheight / sequencesHeight); + int vpy = Math.round(mousey * heightRatio); return vpy - ranges.getViewportHeight() / 2; } @@ -182,10 +200,12 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions public void setDragPoint(int x, int y, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { + resetAlignmentDims(); + // get alignment position of x and box (can get directly from vpranges) and // calculate difference between the positions - int vpx = Math.round((float) x * alwidth / width); - int vpy = Math.round((float) y * alheight / sequencesHeight); + int vpx = Math.round(x * widthRatio); + int vpy = Math.round(y * heightRatio); xdiff = ranges.getStartRes() - vpx; ydiff = ranges.getStartSeq() - vpy; diff --git a/src/jalview/viewmodel/OverviewDimensionsShowHidden.java b/src/jalview/viewmodel/OverviewDimensionsShowHidden.java index 9dde16e..3aa6e1b 100644 --- a/src/jalview/viewmodel/OverviewDimensionsShowHidden.java +++ b/src/jalview/viewmodel/OverviewDimensionsShowHidden.java @@ -72,13 +72,15 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions public void updateViewportFromMouse(int mousex, int mousey, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { + resetAlignmentDims(); + // convert mousex and mousey to alignment units as well as // translating to top left corner of viewport - this is an absolute position int xAsRes = getLeftXFromCentreX(mousex, hiddenCols); int yAsSeq = getTopYFromCentreY(mousey, hiddenSeqs); // convert to visible positions - int visXAsRes = hiddenCols.findColumnPosition(xAsRes); + int visXAsRes = hiddenCols.absoluteToVisibleColumn(xAsRes); yAsSeq = hiddenSeqs.adjustForHiddenSeqs( hiddenSeqs.findIndexWithoutHiddenSeqs(yAsSeq)); yAsSeq = Math.max(yAsSeq, 0); // -1 if before first visible sequence @@ -93,27 +95,32 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions public void adjustViewportFromMouse(int mousex, int mousey, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { + resetAlignmentDims(); + // calculate translation in pixel terms: // get mouse location in viewport coords, add translation in viewport // coords, // convert back to pixel coords int vpx = Math.round((float) mousex * alwidth / width); - int visXAsRes = hiddenCols.findColumnPosition(vpx) + xdiff; + int visXAsRes = hiddenCols.absoluteToVisibleColumn(vpx) + xdiff; - int vpy = Math.round((float) mousey * alheight / sequencesHeight); + int vpy = Math.round(mousey * heightRatio); int visYAsRes = hiddenSeqs.findIndexWithoutHiddenSeqs(vpy) + ydiff; // update viewport accordingly updateViewportFromTopLeft(visXAsRes, visYAsRes, hiddenSeqs, hiddenCols); } + /** + * {@inheritDoc} Callers should have already called resetAlignmentDims to + * refresh alwidth, alheight and width/height ratios + */ @Override protected void updateViewportFromTopLeft(int leftx, int topy, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { int visXAsRes = leftx; int visYAsSeq = topy; - resetAlignmentDims(); if (visXAsRes < 0) { @@ -136,7 +143,7 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions int vpwidth = ranges.getViewportWidth(); // check in case we went off the edge of the alignment - int visAlignWidth = hiddenCols.findColumnPosition(alwidth - 1); + int visAlignWidth = hiddenCols.absoluteToVisibleColumn(alwidth - 1); if (visXAsRes + vpwidth - 1 > visAlignWidth) { // went past the end of the alignment, adjust backwards @@ -144,8 +151,8 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions // if last position was before the end of the alignment, need to update if (ranges.getEndRes() < visAlignWidth) { - visXAsRes = hiddenCols.findColumnPosition(hiddenCols - .subtractVisibleColumns(vpwidth - 1, alwidth - 1)); + visXAsRes = hiddenCols.absoluteToVisibleColumn(hiddenCols + .offsetByVisibleColumns(-(vpwidth - 1), alwidth - 1)); } else { @@ -195,8 +202,8 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions HiddenColumns hiddenCols) { // work with absolute values of startRes and endRes - int startRes = hiddenCols.adjustForHiddenColumns(ranges.getStartRes()); - int endRes = hiddenCols.adjustForHiddenColumns(ranges.getEndRes()); + int startRes = hiddenCols.visibleToAbsoluteColumn(ranges.getStartRes()); + int endRes = hiddenCols.visibleToAbsoluteColumn(ranges.getEndRes()); // work with absolute values of startSeq and endSeq int startSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getStartSeq()); @@ -225,20 +232,32 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions { alwidth = ranges.getAbsoluteAlignmentWidth(); alheight = ranges.getAbsoluteAlignmentHeight(); + + widthRatio = (float) alwidth / width; + heightRatio = (float) alheight / sequencesHeight; } + + /** + * {@inheritDoc} Callers should have already called resetAlignmentDims to + * refresh widthRatio + */ @Override protected int getLeftXFromCentreX(int mousex, HiddenColumns hidden) { int vpx = Math.round((float) mousex * alwidth / width); - return hidden.subtractVisibleColumns(ranges.getViewportWidth() / 2, + return hidden.offsetByVisibleColumns(-ranges.getViewportWidth() / 2, vpx); } + /** + * {@inheritDoc} Callers should have already called resetAlignmentDims to + * refresh heightRatio + */ @Override protected int getTopYFromCentreY(int mousey, HiddenSequences hidden) { - int vpy = Math.round((float) mousey * alheight / sequencesHeight); + int vpy = Math.round(mousey * heightRatio); return hidden.subtractVisibleRows(ranges.getViewportHeight() / 2, vpy); } @@ -246,12 +265,14 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions public void setDragPoint(int x, int y, HiddenSequences hiddenSeqs, HiddenColumns hiddenCols) { + resetAlignmentDims(); + // get alignment position of x and box (can get directly from vpranges) and // calculate difference between the positions - int vpx = Math.round((float) x * alwidth / width); - int vpy = Math.round((float) y * alheight / sequencesHeight); + int vpx = Math.round(x * widthRatio); + int vpy = Math.round(y * heightRatio); - xdiff = ranges.getStartRes() - hiddenCols.findColumnPosition(vpx); + xdiff = ranges.getStartRes() - hiddenCols.absoluteToVisibleColumn(vpx); ydiff = ranges.getStartSeq() - hiddenSeqs.findIndexWithoutHiddenSeqs(vpy); } diff --git a/src/jalview/viewmodel/ViewportRanges.java b/src/jalview/viewmodel/ViewportRanges.java index c7a3fa1..691e492 100644 --- a/src/jalview/viewmodel/ViewportRanges.java +++ b/src/jalview/viewmodel/ViewportRanges.java @@ -609,14 +609,14 @@ public class ViewportRanges extends ViewportProperties } HiddenColumns hidden = al.getHiddenColumns(); - while (x < hidden.adjustForHiddenColumns(startRes)) + while (x < hidden.visibleToAbsoluteColumn(startRes)) { if (!scrollRight(false)) { break; } } - while (x > hidden.adjustForHiddenColumns(endRes)) + while (x > hidden.visibleToAbsoluteColumn(endRes)) { if (!scrollRight(true)) { @@ -638,7 +638,7 @@ public class ViewportRanges extends ViewportProperties boolean changedLocation = false; // convert the x,y location to visible coordinates - int visX = al.getHiddenColumns().findColumnPosition(x); + int visX = al.getHiddenColumns().absoluteToVisibleColumn(x); int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y); // if (vis_x,vis_y) is already visible don't do anything diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 2f30e94..553f813 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -26,6 +26,7 @@ import jalview.api.FeaturesDisplayedI; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.datamodel.features.SequenceFeatures; import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.schemes.FeatureColour; @@ -48,15 +49,48 @@ import java.util.concurrent.ConcurrentHashMap; public abstract class FeatureRendererModel implements jalview.api.FeatureRenderer { + /* + * a data bean to hold one row of feature settings from the gui + */ + public static class FeatureSettingsBean + { + public final String featureType; - /** + public final FeatureColourI featureColour; + + public final FeatureMatcherSetI filter; + + public final Boolean show; + + public FeatureSettingsBean(String type, FeatureColourI colour, + FeatureMatcherSetI theFilter, Boolean isShown) + { + featureType = type; + featureColour = colour; + filter = theFilter; + show = isShown; + } + } + + /* * global transparency for feature */ protected float transparency = 1.0f; - protected Map featureColours = new ConcurrentHashMap(); + /* + * colour scheme for each feature type + */ + protected Map featureColours = new ConcurrentHashMap<>(); - protected Map featureGroups = new ConcurrentHashMap(); + /* + * visibility flag for each feature group + */ + protected Map featureGroups = new ConcurrentHashMap<>(); + + /* + * filters for each feature type + */ + protected Map featureFilters = new HashMap<>(); protected String[] renderOrder; @@ -100,6 +134,7 @@ public abstract class FeatureRendererModel this.renderOrder = frs.renderOrder; this.featureGroups = frs.featureGroups; this.featureColours = frs.featureColours; + this.featureFilters = frs.featureFilters; this.transparency = frs.transparency; this.featureOrder = frs.featureOrder; if (av != null && av != fr.getViewport()) @@ -156,7 +191,7 @@ public abstract class FeatureRendererModel { av.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); } - List nft = new ArrayList(); + List nft = new ArrayList<>(); for (String featureType : featureTypes) { if (!fdi.isRegistered(featureType)) @@ -192,7 +227,7 @@ public abstract class FeatureRendererModel renderOrder = neworder; } - protected Map minmax = new Hashtable(); + protected Map minmax = new Hashtable<>(); public Map getMinMax() { @@ -271,7 +306,7 @@ public abstract class FeatureRendererModel * include features at the position provided their feature type is * displayed, and feature group is null or marked for display */ - List result = new ArrayList(); + List result = new ArrayList<>(); if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null) { return result; @@ -284,9 +319,13 @@ public abstract class FeatureRendererModel List features = sequence.findFeatures(column, column, visibleTypes); + /* + * include features unless their feature group is not displayed, or + * they are hidden (have no colour) based on a filter or colour threshold + */ for (SequenceFeature sf : features) { - if (!featureGroupNotShown(sf)) + if (!featureGroupNotShown(sf) && getColour(sf) != null) { result.add(sf); } @@ -320,7 +359,7 @@ public abstract class FeatureRendererModel } FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed(); - Set oldfeatures = new HashSet(); + Set oldfeatures = new HashSet<>(); if (renderOrder != null) { for (int i = 0; i < renderOrder.length; i++) @@ -333,7 +372,7 @@ public abstract class FeatureRendererModel } AlignmentI alignment = av.getAlignment(); - List allfeatures = new ArrayList(); + List allfeatures = new ArrayList<>(); for (int i = 0; i < alignment.getHeight(); i++) { @@ -413,7 +452,7 @@ public abstract class FeatureRendererModel */ if (minmax == null) { - minmax = new Hashtable(); + minmax = new Hashtable<>(); } synchronized (minmax) { @@ -450,7 +489,7 @@ public abstract class FeatureRendererModel */ private void updateRenderOrder(List allFeatures) { - List allfeatures = new ArrayList(allFeatures); + List allfeatures = new ArrayList<>(allFeatures); String[] oldRender = renderOrder; renderOrder = new String[allfeatures.size()]; boolean initOrders = (featureOrder == null); @@ -477,7 +516,8 @@ public abstract class FeatureRendererModel if (mmrange != null) { FeatureColourI fc = featureColours.get(oldRender[j]); - if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()) + if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled() + && !fc.isColourByAttribute()) { fc.updateBounds(mmrange[0][0], mmrange[0][1]); } @@ -507,7 +547,8 @@ public abstract class FeatureRendererModel if (mmrange != null) { FeatureColourI fc = featureColours.get(newf[i]); - if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()) + if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled() + && !fc.isColourByAttribute()) { fc.updateBounds(mmrange[0][0], mmrange[0][1]); } @@ -557,20 +598,11 @@ public abstract class FeatureRendererModel return fc; } - /** - * Returns the configured colour for a particular feature instance. This - * includes calculation of 'colour by label', or of a graduated score colour, - * if applicable. It does not take into account feature visibility or colour - * transparency. Returns null for a score feature whose score value lies - * outside any colour threshold. - * - * @param feature - * @return - */ + @Override public Color getColour(SequenceFeature feature) { FeatureColourI fc = getFeatureStyle(feature.getType()); - return fc.getColor(feature); + return getColor(feature, fc); } /** @@ -582,7 +614,8 @@ public abstract class FeatureRendererModel */ protected boolean showFeatureOfType(String type) { - return type == null ? false : av.getFeaturesDisplayed().isVisible(type); + return type == null ? false : (av.getFeaturesDisplayed() == null ? true + : av.getFeaturesDisplayed().isVisible(type)); } @Override @@ -617,7 +650,7 @@ public abstract class FeatureRendererModel { if (featureOrder == null) { - featureOrder = new Hashtable(); + featureOrder = new Hashtable<>(); } featureOrder.put(type, new Float(position)); return position; @@ -651,32 +684,33 @@ public abstract class FeatureRendererModel * Replace current ordering with new ordering * * @param data - * { String(Type), Colour(Type), Boolean(Displayed) } + * an array of { Type, Colour, Filter, Boolean } * @return true if any visible features have been reordered, else false */ - public boolean setFeaturePriority(Object[][] data) + public boolean setFeaturePriority(FeatureSettingsBean[] data) { return setFeaturePriority(data, true); } /** - * Sets the priority order for features, with the highest priority (displayed - * on top) at the start of the data array + * Sets the priority order for features, with the highest priority (displayed on + * top) at the start of the data array * * @param data - * { String(Type), Colour(Type), Boolean(Displayed) } + * an array of { Type, Colour, Filter, Boolean } * @param visibleNew * when true current featureDisplay list will be cleared - * @return true if any visible features have been reordered or recoloured, - * else false (i.e. no need to repaint) + * @return true if any visible features have been reordered or recoloured, else + * false (i.e. no need to repaint) */ - public boolean setFeaturePriority(Object[][] data, boolean visibleNew) + public boolean setFeaturePriority(FeatureSettingsBean[] data, + boolean visibleNew) { /* * note visible feature ordering and colours before update */ List visibleFeatures = getDisplayedFeatureTypes(); - Map visibleColours = new HashMap( + Map visibleColours = new HashMap<>( getFeatureColours()); FeaturesDisplayedI av_featuresdisplayed = null; @@ -709,9 +743,9 @@ public abstract class FeatureRendererModel { for (int i = 0; i < data.length; i++) { - String type = data[i][0].toString(); - setColour(type, (FeatureColourI) data[i][1]); - if (((Boolean) data[i][2]).booleanValue()) + String type = data[i].featureType; + setColour(type, data[i].featureColour); + if (data[i].show) { av_featuresdisplayed.setVisible(type); } @@ -836,7 +870,7 @@ public abstract class FeatureRendererModel { if (featureGroups != null) { - List gp = new ArrayList(); + List gp = new ArrayList<>(); for (String grp : featureGroups.keySet()) { @@ -882,7 +916,7 @@ public abstract class FeatureRendererModel @Override public Map getDisplayedFeatureCols() { - Map fcols = new Hashtable(); + Map fcols = new Hashtable<>(); if (getViewport().getFeaturesDisplayed() == null) { return fcols; @@ -910,7 +944,7 @@ public abstract class FeatureRendererModel public List getDisplayedFeatureTypes() { List typ = getRenderOrder(); - List displayed = new ArrayList(); + List displayed = new ArrayList<>(); FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed(); if (feature_disp != null) { @@ -931,7 +965,7 @@ public abstract class FeatureRendererModel @Override public List getDisplayedFeatureGroups() { - List _gps = new ArrayList(); + List _gps = new ArrayList<>(); for (String gp : getFeatureGroups()) { if (checkGroupVisibility(gp, false)) @@ -966,7 +1000,7 @@ public abstract class FeatureRendererModel public List findFeaturesAtResidue(SequenceI sequence, int resNo) { - List result = new ArrayList(); + List result = new ArrayList<>(); if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null) { return result; @@ -986,7 +1020,7 @@ public abstract class FeatureRendererModel for (SequenceFeature sf : features) { - if (!featureGroupNotShown(sf)) + if (!featureGroupNotShown(sf) && getColour(sf) != null) { result.add(sf); } @@ -995,35 +1029,34 @@ public abstract class FeatureRendererModel } /** - * Removes from the list of features any that have a feature group that is not - * displayed, or duplicate the location of a feature of the same type (unless - * a graduated colour scheme or colour by label is applied). Should be used - * only for features of the same feature colour (which normally implies the - * same feature type). + * Removes from the list of features any that duplicate the location of a + * feature of the same type. Should be used only for features of the same, + * simple, feature colour (which normally implies the same feature type). Does + * not check visibility settings for feature type or feature group. No + * filtering is done if transparency, or any feature filters, are in force. * * @param features - * @param fc */ - public void filterFeaturesForDisplay(List features, - FeatureColourI fc) + public void filterFeaturesForDisplay(List features) { - if (features.isEmpty()) + /* + * don't remove 'redundant' features if + * - transparency is applied (feature count affects depth of feature colour) + * - filters are applied (not all features may be displayable) + */ + if (features.isEmpty() || transparency != 1f + || !featureFilters.isEmpty()) { return; } + SequenceFeatures.sortFeatures(features, true); - boolean simpleColour = fc == null || fc.isSimpleColour(); SequenceFeature lastFeature = null; Iterator it = features.iterator(); while (it.hasNext()) { SequenceFeature sf = it.next(); - if (featureGroupNotShown(sf)) - { - it.remove(); - continue; - } /* * a feature is redundant for rendering purposes if it has the @@ -1031,18 +1064,90 @@ public abstract class FeatureRendererModel * (checking type and isContactFeature as a fail-safe here, although * currently they are guaranteed to match in this context) */ - if (simpleColour) + if (lastFeature != null && sf.getBegin() == lastFeature.getBegin() + && sf.getEnd() == lastFeature.getEnd() + && sf.isContactFeature() == lastFeature.isContactFeature() + && sf.getType().equals(lastFeature.getType())) { - if (lastFeature != null && sf.getBegin() == lastFeature.getBegin() - && sf.getEnd() == lastFeature.getEnd() - && sf.isContactFeature() == lastFeature.isContactFeature() - && sf.getType().equals(lastFeature.getType())) - { - it.remove(); - } + it.remove(); } lastFeature = sf; } } + @Override + public Map getFeatureFilters() + { + return featureFilters; + } + + @Override + public void setFeatureFilters(Map filters) + { + featureFilters = filters; + } + + @Override + public FeatureMatcherSetI getFeatureFilter(String featureType) + { + return featureFilters.get(featureType); + } + + @Override + public void setFeatureFilter(String featureType, FeatureMatcherSetI filter) + { + if (filter == null || filter.isEmpty()) + { + featureFilters.remove(featureType); + } + else + { + featureFilters.put(featureType, filter); + } + } + + /** + * Answers the colour for the feature, or null if the feature is excluded by + * feature group visibility, by filters, or by colour threshold settings. This + * method does not take feature visibility into account. + * + * @param sf + * @param fc + * @return + */ + public Color getColor(SequenceFeature sf, FeatureColourI fc) + { + /* + * is the feature group displayed? + */ + if (featureGroupNotShown(sf)) + { + return null; + } + + /* + * does the feature pass filters? + */ + if (!featureMatchesFilters(sf)) + { + return null; + } + + return fc.getColor(sf); + } + + /** + * Answers true if there no are filters defined for the feature type, or this + * feature matches the filters. Answers false if the feature fails to match + * filters. + * + * @param sf + * @return + */ + protected boolean featureMatchesFilters(SequenceFeature sf) + { + FeatureMatcherSetI filter = featureFilters.get(sf.getType()); + return filter == null ? true : filter.matches(sf); + } + } diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java index dc2ae11..f594453 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java @@ -21,9 +21,11 @@ package jalview.viewmodel.seqfeatures; import jalview.api.FeatureColourI; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.schemes.FeatureColour; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -42,6 +44,11 @@ public class FeatureRendererSettings implements Cloneable */ Map featureColours; + /* + * map of {featureType, filters} + */ + Map featureFilters; + float transparency; Map featureOrder; @@ -72,7 +79,9 @@ public class FeatureRendererSettings implements Cloneable renderOrder = null; featureGroups = new ConcurrentHashMap(); featureColours = new ConcurrentHashMap(); + featureFilters = new HashMap<>(); featureOrder = new ConcurrentHashMap(); + if (fr.renderOrder != null) { this.renderOrder = new String[fr.renderOrder.length]; @@ -100,6 +109,12 @@ public class FeatureRendererSettings implements Cloneable featureColours.put(next, new FeatureColour((FeatureColour) val)); } } + + if (fr.featureFilters != null) + { + this.featureFilters.putAll(fr.featureFilters); + } + this.transparency = fr.transparency; if (fr.featureOrder != null) { diff --git a/src/jalview/ws/DBRefFetcher.java b/src/jalview/ws/DBRefFetcher.java index 5dd8bf3..41e4d0d 100644 --- a/src/jalview/ws/DBRefFetcher.java +++ b/src/jalview/ws/DBRefFetcher.java @@ -28,15 +28,12 @@ import jalview.datamodel.DBRefSource; import jalview.datamodel.Mapping; import jalview.datamodel.SequenceI; import jalview.gui.CutAndPasteTransfer; -import jalview.gui.DasSourceBrowser; import jalview.gui.Desktop; import jalview.gui.FeatureSettings; import jalview.gui.IProgressIndicator; import jalview.gui.OOMWarning; import jalview.util.DBRefUtils; import jalview.util.MessageManager; -import jalview.ws.dbsources.das.api.jalviewSourceI; -import jalview.ws.dbsources.das.datamodel.DasSequenceSource; import jalview.ws.seqfetcher.DbSourceProxy; import java.util.ArrayList; @@ -61,6 +58,8 @@ public class DBRefFetcher implements Runnable { private static final String NEWLINE = System.lineSeparator(); + public static final String TRIM_RETRIEVED_SEQUENCES = "TRIM_FETCHED_DATASET_SEQS"; + public interface FetchFinishedListenerI { void finished(); @@ -117,7 +116,7 @@ public class DBRefFetcher implements Runnable DbSourceProxy[] sources, FeatureSettings featureSettings, boolean isNucleotide) { - listeners = new ArrayList(); + listeners = new ArrayList<>(); this.progressWindow = progressIndicatorFrame; alseqs = new SequenceI[seqs.length]; SequenceI[] ds = new SequenceI[seqs.length]; @@ -139,7 +138,7 @@ public class DBRefFetcher implements Runnable .getSequenceFetcherSingleton(progressIndicatorFrame); // set default behaviour for transferring excess sequence data to the // dataset - trimDsSeqs = Cache.getDefault("TRIM_FETCHED_DATASET_SEQS", true); + trimDsSeqs = Cache.getDefault(TRIM_RETRIEVED_SEQUENCES, true); if (sources == null) { setDatabaseSources(featureSettings, isNucleotide); @@ -163,23 +162,7 @@ public class DBRefFetcher implements Runnable { // af.featureSettings_actionPerformed(null); String[] defdb = null; - List selsources = new ArrayList(); - Vector dasselsrc = (featureSettings != null) - ? featureSettings.getSelectedSources() - : new DasSourceBrowser().getSelectedSources(); - - for (jalviewSourceI src : dasselsrc) - { - List sp = src.getSequenceSourceProxies(); - if (sp != null) - { - selsources.addAll(sp); - if (sp.size() > 1) - { - Cache.log.debug("Added many Db Sources for :" + src.getTitle()); - } - } - } + List selsources = new ArrayList<>(); // select appropriate databases based on alignFrame context. if (forNucleotide) { @@ -189,7 +172,7 @@ public class DBRefFetcher implements Runnable { defdb = DBRefSource.PROTEINDBS; } - List srces = new ArrayList(); + List srces = new ArrayList<>(); for (String ddb : defdb) { List srcesfordb = sfetcher.getSourceProxy(ddb); @@ -233,30 +216,6 @@ public class DBRefFetcher implements Runnable } /** - * retrieve all the das sequence sources and add them to the list of db - * sources to retrieve from - */ - public void appendAllDasSources() - { - if (dbSources == null) - { - dbSources = new DbSourceProxy[0]; - } - // append additional sources - DbSourceProxy[] otherdb = sfetcher - .getDbSourceProxyInstances(DasSequenceSource.class); - if (otherdb != null && otherdb.length > 0) - { - DbSourceProxy[] newsrc = new DbSourceProxy[dbSources.length - + otherdb.length]; - System.arraycopy(dbSources, 0, newsrc, 0, dbSources.length); - System.arraycopy(otherdb, 0, newsrc, dbSources.length, - otherdb.length); - dbSources = newsrc; - } - } - - /** * start the fetcher thread * * @param waitTillFinished @@ -309,14 +268,14 @@ public class DBRefFetcher implements Runnable } else if (seqs == null) { - seqs = new Vector(); + seqs = new Vector<>(); seqs.addElement(seq); } } else { - seqs = new Vector(); + seqs = new Vector<>(); seqs.addElement(seq); } @@ -355,9 +314,9 @@ public class DBRefFetcher implements Runnable e.printStackTrace(); } - Vector sdataset = new Vector( + Vector sdataset = new Vector<>( Arrays.asList(dataset)); - List warningMessages = new ArrayList(); + List warningMessages = new ArrayList<>(); int db = 0; while (sdataset.size() > 0 && db < dbSources.length) @@ -369,8 +328,8 @@ public class DBRefFetcher implements Runnable SequenceI[] currSeqs = new SequenceI[sdataset.size()]; sdataset.copyInto(currSeqs);// seqs that are to be validated against // dbSources[db] - Vector queries = new Vector(); // generated queries curSeq - seqRefs = new Hashtable>(); + Vector queries = new Vector<>(); // generated queries curSeq + seqRefs = new Hashtable<>(); int seqIndex = 0; @@ -572,7 +531,7 @@ public class DBRefFetcher implements Runnable { // Work out which sequences this sequence matches, // taking into account all accessionIds and names in the file - Vector sequenceMatches = new Vector(); + Vector sequenceMatches = new Vector<>(); // look for corresponding accession ids DBRefEntry[] entryRefs = DBRefUtils .selectRefs(retrievedSeq.getDBRefs(), new String[] @@ -825,7 +784,7 @@ public class DBRefFetcher implements Runnable */ private SequenceI[] recoverDbSequences(SequenceI[] sequencesArray) { - Vector nseq = new Vector(); + Vector nseq = new Vector<>(); for (int i = 0; sequencesArray != null && i < sequencesArray.length; i++) { diff --git a/src/jalview/ws/DasSequenceFeatureFetcher.java b/src/jalview/ws/DasSequenceFeatureFetcher.java deleted file mode 100644 index c661e2c..0000000 --- a/src/jalview/ws/DasSequenceFeatureFetcher.java +++ /dev/null @@ -1,925 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.ws; - -import jalview.bin.Cache; -import jalview.datamodel.DBRefEntry; -import jalview.datamodel.DBRefSource; -import jalview.datamodel.SequenceFeature; -import jalview.datamodel.SequenceI; -import jalview.gui.AlignFrame; -import jalview.gui.Desktop; -import jalview.gui.FeatureSettings; -import jalview.gui.JvOptionPane; -import jalview.util.DBRefUtils; -import jalview.util.MessageManager; -import jalview.util.UrlLink; -import jalview.ws.dbsources.das.api.DasSourceRegistryI; -import jalview.ws.dbsources.das.api.jalviewSourceI; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.Vector; - -import org.biodas.jdas.client.FeaturesClient; -import org.biodas.jdas.client.adapters.features.DasGFFAdapter; -import org.biodas.jdas.client.adapters.features.DasGFFAdapter.GFFAdapter; -import org.biodas.jdas.client.threads.FeaturesClientMultipleSources; -import org.biodas.jdas.schema.features.ERRORSEGMENT; -import org.biodas.jdas.schema.features.FEATURE; -import org.biodas.jdas.schema.features.LINK; -import org.biodas.jdas.schema.features.SEGMENT; -import org.biodas.jdas.schema.features.TYPE; -import org.biodas.jdas.schema.features.UNKNOWNFEATURE; -import org.biodas.jdas.schema.features.UNKNOWNSEGMENT; -import org.biodas.jdas.schema.sources.COORDINATES; - -/** - * DOCUMENT ME! - * - * @author $author$ - * @version $Revision$ - */ -public class DasSequenceFeatureFetcher -{ - SequenceI[] sequences; - - AlignFrame af; - - FeatureSettings fsettings; - - StringBuffer sbuffer = new StringBuffer(); - - List selectedSources; - - boolean cancelled = false; - - private void debug(String mesg) - { - debug(mesg, null); - } - - private void debug(String mesg, Exception e) - { - if (Cache.log != null) - { - Cache.log.debug(mesg, e); - } - else - { - System.err.println(mesg); - if (e != null) - { - e.printStackTrace(); - } - } - } - - long startTime; - - private DasSourceRegistryI sourceRegistry; - - private boolean useJDASMultiThread = true; - - /** - * Creates a new SequenceFeatureFetcher object. Uses default - * - * @param align - * DOCUMENT ME! - * @param ap - * DOCUMENT ME! - */ - public DasSequenceFeatureFetcher(SequenceI[] sequences, - FeatureSettings fsettings, Vector selectedSources) - { - this(sequences, fsettings, selectedSources, true, true, true); - } - - public DasSequenceFeatureFetcher(SequenceI[] oursequences, - FeatureSettings fsettings, List selectedSources2, - boolean checkDbrefs, boolean promptFetchDbrefs) - { - this(oursequences, fsettings, selectedSources2, checkDbrefs, - promptFetchDbrefs, true); - } - - public DasSequenceFeatureFetcher(SequenceI[] oursequences, - FeatureSettings fsettings, List selectedSources2, - boolean checkDbrefs, boolean promptFetchDbrefs, - boolean useJDasMultiThread) - { - this.useJDASMultiThread = useJDasMultiThread; - this.selectedSources = new ArrayList<>(); - // filter both sequences and sources to eliminate duplicates - for (jalviewSourceI src : selectedSources2) - { - if (!selectedSources.contains(src)) - { - selectedSources.add(src); - } - ; - } - Vector sqs = new Vector(); - for (int i = 0; i < oursequences.length; i++) - { - if (!sqs.contains(oursequences[i])) - { - sqs.addElement(oursequences[i]); - } - } - sequences = new SequenceI[sqs.size()]; - for (int i = 0; i < sequences.length; i++) - { - sequences[i] = (SequenceI) sqs.elementAt(i); - } - if (fsettings != null) - { - this.fsettings = fsettings; - this.af = fsettings.af; - af.setShowSeqFeatures(true); - } - int uniprotCount = 0; - for (jalviewSourceI source : selectedSources) - { - for (COORDINATES coords : source.getVersion().getCOORDINATES()) - { - // TODO: match UniProt coord system canonically (?) - does - // UniProt==uniprot==UNIPROT ? - if (coords.getAuthority().toLowerCase().equals("uniprot")) - { - uniprotCount++; - break; - } - } - } - - int refCount = 0; - for (int i = 0; i < sequences.length; i++) - { - DBRefEntry[] dbref = sequences[i].getDBRefs(); - if (dbref != null) - { - for (int j = 0; j < dbref.length; j++) - { - if (dbref[j].getSource().equals(DBRefSource.UNIPROT)) - { - refCount++; - break; - } - } - } - } - - if (checkDbrefs && refCount < sequences.length && uniprotCount > 0) - { - - int reply = JvOptionPane.YES_OPTION; - if (promptFetchDbrefs) - { - reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop, - MessageManager.getString( - "info.you_want_jalview_to_find_uniprot_accessions"), - MessageManager - .getString("label.find_uniprot_accession_ids"), - JvOptionPane.YES_NO_OPTION, JvOptionPane.QUESTION_MESSAGE); - } - - if (reply == JvOptionPane.YES_OPTION) - { - Thread thread = new Thread(new FetchDBRefs()); - thread.start(); - } - else - { - _startFetching(); - } - } - else - { - _startFetching(); - } - - } - - private void _startFetching() - { - running = true; - new Thread(new FetchSeqFeatures()).start(); - } - - class FetchSeqFeatures implements Runnable - { - @Override - public void run() - { - startFetching(); - setGuiFetchComplete(); - } - } - - class FetchDBRefs implements Runnable - { - @Override - public void run() - { - running = true; - boolean isNucleotide = af.getViewport().getAlignment().isNucleotide(); - new DBRefFetcher(sequences, af, null, af.featureSettings, - isNucleotide).fetchDBRefs(true); - - startFetching(); - setGuiFetchComplete(); - } - } - - /** - * Spawns Fetcher threads to add features to sequences in the dataset - */ - void startFetching() - { - running = true; - cancelled = false; - startTime = System.currentTimeMillis(); - if (af != null) - { - af.setProgressBar(MessageManager.getString( - "status.fetching_das_sequence_features"), startTime); - } - if (sourceRegistry == null) - { - sourceRegistry = Cache.getDasSourceRegistry(); - } - if (selectedSources == null || selectedSources.size() == 0) - { - try - { - jalviewSourceI[] sources = sourceRegistry.getSources() - .toArray(new jalviewSourceI[0]); - String active = Cache.getDefault("DAS_ACTIVE_SOURCE", "uniprot"); - StringTokenizer st = new StringTokenizer(active, "\t"); - selectedSources = new Vector(); - String token; - while (st.hasMoreTokens()) - { - token = st.nextToken(); - for (int i = 0; i < sources.length; i++) - { - if (sources[i].getTitle().equals(token)) - { - selectedSources.add(sources[i]); - break; - } - } - } - } catch (Exception ex) - { - debug("Exception whilst setting default feature sources from registry and local preferences.", - ex); - } - } - - if (selectedSources == null || selectedSources.size() == 0) - { - System.out.println("No DAS Sources active"); - cancelled = true; - setGuiNoDassourceActive(); - return; - } - - sourcesRemaining = selectedSources.size(); - FeaturesClientMultipleSources fc = new FeaturesClientMultipleSources(); - fc.setConnProps(sourceRegistry.getSessionHandler()); - // Now sending requests one at a time to each server - ArrayList srcobj = new ArrayList<>(); - ArrayList src = new ArrayList<>(); - List> ids = new ArrayList<>(); - List> idobj = new ArrayList<>(); - List> sqset = new ArrayList<>(); - for (jalviewSourceI _sr : selectedSources) - { - - Map slist = new HashMap<>(); - List idob = new ArrayList<>(); - List qset = new ArrayList<>(); - - for (SequenceI seq : sequences) - { - Object[] idset = nextSequence(_sr, seq); - if (idset != null) - { - List _idob = (List) idset[0]; - List _qset = (List) idset[1]; - if (_idob.size() > 0) - { - // add sequence's ref for each id derived from it - // (space inefficient, but most unambiguous) - // could replace with hash with _qset values as keys. - Iterator dbobj = _idob.iterator(); - for (String q : _qset) - { - SequenceI osq = slist.get(q); - DBRefEntry dr = dbobj.next(); - if (osq != null && osq != seq) - { - // skip - non-canonical query - } - else - { - idob.add(dr); - qset.add(q); - slist.put(q, seq); - } - } - } - } - } - if (idob.size() > 0) - { - srcobj.add(_sr); - src.add(_sr.getSourceURL()); - ids.add(qset); - idobj.add(idob); - sqset.add(slist); - } - } - Map, Exception>> errors = new HashMap<>(); - Map, DasGFFAdapter>> results = new HashMap<>(); - if (!useJDASMultiThread) - { - Iterator sources = src.iterator(); - // iterate over each query for each source and do each one individually - for (List idl : ids) - { - String source = sources.next(); - FeaturesClient featuresc = new FeaturesClient( - sourceRegistry.getSessionHandler() - .getConnectionPropertyProviderFor(source)); - for (String id : idl) - { - List qid = Arrays.asList(new String[] { id }); - try - { - DasGFFAdapter dga = featuresc.fetchData(source, qid); - Map, DasGFFAdapter> ers = results.get(source); - if (ers == null) - { - results.put(source, - ers = new HashMap<>()); - } - ers.put(qid, dga); - } catch (Exception ex) - { - Map, Exception> ers = errors.get(source); - if (ers == null) - { - errors.put(source, - ers = new HashMap<>()); - } - ers.put(qid, ex); - } - } - } - } - else - { - // pass them all at once - fc.fetchData(src, ids, false, results, errors); - fc.shutDown(); - while (!fc.isTerminated()) - { - try - { - Thread.sleep(200); - } catch (InterruptedException x) - { - - } - } - } - Iterator> idset = ids.iterator(); - Iterator> idobjset = idobj.iterator(); - Iterator> seqset = sqset.iterator(); - for (jalviewSourceI source : srcobj) - { - processResponse(seqset.next(), source, idset.next(), idobjset.next(), - results.get(source.getSourceURL()), - errors.get(source.getSourceURL())); - } - } - - private void processResponse(Map sequencemap, - jalviewSourceI jvsource, List ids, List idobj, - Map, DasGFFAdapter> results, - Map, Exception> errors) - { - Set sequences = new HashSet<>(); - String source = jvsource.getSourceURL(); - // process features - DasGFFAdapter result = (results == null) ? null : results.get(ids); - Exception error = (errors == null) ? null : errors.get(ids); - if (result == null) - { - debug("das source " + source + " could not be contacted. " - + (error == null ? "" : error.toString())); - } - else - { - - GFFAdapter gff = result.getGFF(); - List segments = gff.getSegments(); - List errorsegs = gff.getErrorSegments(); - List unkfeats = gff.getUnknownFeatures(); - List unksegs = gff.getUnknownSegments(); - debug("das source " + source + " returned " + gff.getTotal() - + " responses. " + (errorsegs != null ? errorsegs.size() : 0) - + " were incorrect segment queries, " - + (unkfeats != null ? unkfeats.size() : 0) - + " were unknown features " - + (unksegs != null ? unksegs.size() : 0) - + " were unknown segments and " - + (segments != null ? segments.size() : 0) - + " were segment responses."); - Iterator dbr = idobj.iterator(); - if (segments != null) - { - for (SEGMENT seg : segments) - { - String id = seg.getId(); - if (ids.indexOf(id) == -1) - { - id = id.toUpperCase(); - } - DBRefEntry dbref = idobj.get(ids.indexOf(id)); - SequenceI sequence = sequencemap.get(id); - boolean added = false; - sequences.add(sequence); - - for (FEATURE feat : seg.getFEATURE()) - { - // standard DAS feature-> jalview sequence feature transformation - SequenceFeature f = newSequenceFeature(feat, - jvsource.getTitle()); - if (!parseSeqFeature(sequence, f, feat, jvsource)) - { - if (dbref.getMap() != null && f.getBegin() > 0 - && f.getEnd() > 0) - { - debug("mapping from " + f.getBegin() + " - " + f.getEnd()); - SequenceFeature vf[] = null; - - try - { - vf = dbref.getMap().locateFeature(f); - } catch (Exception ex) - { - Cache.log.warn( - "Error in 'experimental' mapping of features. Please try to reproduce and then report info to jalview-discuss@jalview.org."); - Cache.log.warn("Mapping feature from " + f.getBegin() - + " to " + f.getEnd() + " in dbref " - + dbref.getAccessionId() + " in " - + dbref.getSource()); - Cache.log.warn("using das Source " + source); - Cache.log.warn("Exception", ex); - } - - if (vf != null) - { - for (int v = 0; v < vf.length; v++) - { - debug("mapping to " + v + ": " + vf[v].getBegin() - + " - " + vf[v].getEnd()); - sequence.addSequenceFeature(vf[v]); - } - } - } - else - { - sequence.addSequenceFeature(f); - } - } - } - } - featuresAdded(sequences); - } - else - { - // System.out.println("No features found for " + seq.getName() - // + " from: " + e.getDasSource().getNickname()); - } - } - } - - private void setGuiNoDassourceActive() - { - - if (af != null) - { - af.setProgressBar( - MessageManager.getString("status.no_das_sources_active"), - startTime); - } - if (getFeatSettings() != null) - { - fsettings.noDasSourceActive(); - } - } - - /** - * Update our fsettings dialog reference if we didn't have one when we were - * first initialised. - * - * @return fsettings - */ - private FeatureSettings getFeatSettings() - { - if (fsettings == null) - { - if (af != null) - { - fsettings = af.featureSettings; - } - } - return fsettings; - } - - public void cancel() - { - if (af != null) - { - af.setProgressBar(MessageManager.getString( - "status.das_feature_fetching_cancelled"), startTime); - } - cancelled = true; - } - - int sourcesRemaining = 0; - - private boolean running = false; - - private void setGuiFetchComplete() - { - running = false; - if (!cancelled && af != null) - { - // only update the progress bar if we've completed the fetch normally - af.setProgressBar(MessageManager.getString( - "status.das_feature_fetching_complete"), startTime); - } - - if (af != null && af.featureSettings != null) - { - af.featureSettings.discoverAllFeatureData(); - } - - if (getFeatSettings() != null) - { - fsettings.complete(); - } - } - - void featuresAdded(Set seqs) - { - if (af == null) - { - // no gui to update with features. - return; - } - af.getFeatureRenderer().featuresAdded(); - - int start = af.getViewport().getRanges().getStartSeq(); - int end = af.getViewport().getRanges().getEndSeq(); - int index; - for (index = start; index < end; index++) - { - for (SequenceI seq : seqs) - { - if (seq == af.getViewport().getAlignment().getSequenceAt(index) - .getDatasetSequence()) - { - af.alignPanel.paintAlignment(true, true); - index = end; - break; - } - } - } - } - - Object[] nextSequence(jalviewSourceI dasSource, SequenceI seq) - { - if (cancelled) - { - return null; - } - DBRefEntry[] uprefs = DBRefUtils.selectRefs(seq.getDBRefs(), - new String[] - { - // jalview.datamodel.DBRefSource.PDB, - DBRefSource.UNIPROT, - // jalview.datamodel.DBRefSource.EMBL - not tested on any EMBL coord - // sys sources - }); - // TODO: minimal list of DAS queries to make by querying with untyped ID if - // distinct from any typed IDs - - List ids = new ArrayList<>(); - List qstring = new ArrayList<>(); - boolean dasCoordSysFound = false; - - if (uprefs != null) - { - // do any of these ids match the source's coordinate system ? - for (int j = 0; !dasCoordSysFound && j < uprefs.length; j++) - { - - for (COORDINATES csys : dasSource.getVersion().getCOORDINATES()) - { - if (DBRefUtils.isDasCoordinateSystem(csys.getAuthority(), - uprefs[j])) - { - debug("Launched fetcher for coordinate system " - + csys.getAuthority()); - // Will have to pass any mapping information to the fetcher - // - the start/end for the DBRefEntry may not be the same as the - // sequence's start/end - - System.out.println( - seq.getName() + " " + (seq.getDatasetSequence() == null) - + " " + csys.getUri()); - - dasCoordSysFound = true; // break's out of the loop - ids.add(uprefs[j]); - qstring.add(uprefs[j].getAccessionId()); - } - else - { - System.out.println("IGNORE " + csys.getAuthority()); - } - } - } - } - - if (!dasCoordSysFound) - { - String id = null; - // try and use the name as the sequence id - if (seq.getName().indexOf("|") > -1) - { - id = seq.getName().substring(seq.getName().lastIndexOf("|") + 1); - if (id.trim().length() < 4) - { - // hack - we regard a significant ID as being at least 4 - // non-whitespace characters - id = seq.getName().substring(0, seq.getName().lastIndexOf("|")); - if (id.indexOf("|") > -1) - { - id = id.substring(id.lastIndexOf("|") + 1); - } - } - } - else - { - id = seq.getName(); - } - if (id != null) - { - DBRefEntry dbre = new DBRefEntry(); - dbre.setAccessionId(id); - // Should try to call a general feature fetcher that - // queries many sources with name to discover applicable ID references - ids.add(dbre); - qstring.add(dbre.getAccessionId()); - } - } - - return new Object[] { ids, qstring }; - } - - /** - * examine the given sequence feature to determine if it should actually be - * turned into sequence annotation or database cross references rather than a - * simple sequence feature. - * - * @param seq - * the sequence to annotate - * @param f - * the jalview sequence feature generated from the DAS feature - * @param map - * the sequence feature attributes - * @param source - * the source that emitted the feature - * @return true if feature was consumed as another kind of annotation. - */ - protected boolean parseSeqFeature(SequenceI seq, SequenceFeature f, - FEATURE feature, jalviewSourceI source) - { - SequenceI mseq = seq; - while (seq.getDatasetSequence() != null) - { - seq = seq.getDatasetSequence(); - } - if (f.getType() != null) - { - String type = f.getType(); - if (type.equalsIgnoreCase("protein_name")) - { - // parse name onto the alignment sequence or the dataset sequence. - if (seq.getDescription() == null - || seq.getDescription().trim().length() == 0) - { - // could look at the note series to pick out the first long name, for - // the moment just use the whole description string - seq.setDescription(f.getDescription()); - } - if (mseq.getDescription() == null - || mseq.getDescription().trim().length() == 0) - { - // could look at the note series to pick out the first long name, for - // the moment just use the whole description string - mseq.setDescription(f.getDescription()); - } - return true; - } - // check if source has biosapiens or other sequence ontology label - if (type.equalsIgnoreCase("DBXREF") || type.equalsIgnoreCase("DBREF")) - { - // try to parse the accession out - - DBRefEntry dbr = new DBRefEntry(); - dbr.setVersion(source.getTitle()); - StringTokenizer st = new StringTokenizer(f.getDescription(), ":"); - if (st.hasMoreTokens()) - { - dbr.setSource(st.nextToken()); - } - if (st.hasMoreTokens()) - { - dbr.setAccessionId(st.nextToken()); - } - seq.addDBRef(dbr); - - if (f.links != null && f.links.size() > 0) - { - // feature is also appended to enable links to be seen. - // TODO: consider extending dbrefs to have their own links ? - // TODO: new feature: extract dbref links from DAS servers and add the - // URL pattern to the list of DB name associated links in the user's - // preferences ? - // for the moment - just fix up the existing feature so it displays - // correctly. - // f.setType(dbr.getSource()); - // f.setDescription(); - f.setValue("linkonly", Boolean.TRUE); - // f.setDescription(""); - Vector newlinks = new Vector(); - Enumeration it = f.links.elements(); - while (it.hasMoreElements()) - { - String elm; - UrlLink urllink = new UrlLink(elm = (String) it.nextElement()); - if (urllink.isValid()) - { - urllink.setLabel(f.getDescription()); - newlinks.addElement(urllink.toString()); - } - else - { - // couldn't parse the link properly. Keep it anyway - just in - // case. - debug("couldn't parse link string - " + elm); - newlinks.addElement(elm); - } - } - f.links = newlinks; - seq.addSequenceFeature(f); - } - return true; - } - } - return false; - } - - /** - * creates a jalview sequence feature from a das feature document - * - * @param feat - * @return sequence feature object created using dasfeature information - */ - SequenceFeature newSequenceFeature(FEATURE feat, String nickname) - { - if (feat == null) - { - return null; - } - try - { - /** - * Different qNames for a DAS Feature - are string keys to the HashMaps in - * features "METHOD") || qName.equals("TYPE") || qName.equals("START") || - * qName.equals("END") || qName.equals("NOTE") || qName.equals("LINK") || - * qName.equals("SCORE") - */ - String desc = new String(); - if (feat.getNOTE() != null) - { - for (String note : feat.getNOTE()) - { - desc += note; - } - } - - int start = 0, end = 0; - float score = 0f; - - try - { - start = Integer.parseInt(feat.getSTART().toString()); - } catch (Exception ex) - { - } - try - { - end = Integer.parseInt(feat.getEND().toString()); - } catch (Exception ex) - { - } - try - { - Object scr = feat.getSCORE(); - if (scr != null) - { - score = (float) Double.parseDouble(scr.toString()); - - } - } catch (Exception ex) - { - } - - SequenceFeature f = new SequenceFeature(getTypeString(feat.getTYPE()), - desc, start, end, score, nickname); - - if (feat.getLINK() != null) - { - for (LINK link : feat.getLINK()) - { - // Do not put feature extent in link text for non-positional features - if (f.begin == 0 && f.end == 0) - { - f.addLink(f.getType() + " " + link.getContent() + "|" - + link.getHref()); - } - else - { - f.addLink(f.getType() + " " + f.begin + "_" + f.end + " " - + link.getContent() + "|" + link.getHref()); - } - } - } - - return f; - } catch (Exception e) - { - System.out.println("ERRR " + e); - e.printStackTrace(); - System.out.println("############"); - debug("Failed to parse " + feat.toString(), e); - return null; - } - } - - private String getTypeString(TYPE type) - { - return type.getContent(); - } - - public boolean isRunning() - { - return running; - } - -} diff --git a/src/jalview/ws/SequenceFetcher.java b/src/jalview/ws/SequenceFetcher.java index a0b77de..29d4ec7 100644 --- a/src/jalview/ws/SequenceFetcher.java +++ b/src/jalview/ws/SequenceFetcher.java @@ -29,12 +29,10 @@ import jalview.ws.dbsources.PfamFull; import jalview.ws.dbsources.PfamSeed; import jalview.ws.dbsources.RfamSeed; import jalview.ws.dbsources.Uniprot; -import jalview.ws.dbsources.das.api.jalviewSourceI; import jalview.ws.seqfetcher.ASequenceFetcher; import jalview.ws.seqfetcher.DbSourceProxy; import java.util.ArrayList; -import java.util.List; /** * This implements the run-time discovery of sequence database clients. @@ -50,11 +48,6 @@ public class SequenceFetcher extends ASequenceFetcher */ public SequenceFetcher() { - this(true); - } - - public SequenceFetcher(boolean addDas) - { addDBRefSourceImpl(EnsemblGene.class); addDBRefSourceImpl(EnsemblGenomes.class); addDBRefSourceImpl(EmblSource.class); @@ -64,26 +57,19 @@ public class SequenceFetcher extends ASequenceFetcher addDBRefSourceImpl(PfamFull.class); addDBRefSourceImpl(PfamSeed.class); addDBRefSourceImpl(RfamSeed.class); - - if (addDas) - { - registerDasSequenceSources(); - } } /** - * return an ordered list of database sources where non-das database classes - * appear before das database classes + * return an ordered list of database sources excluding alignment only databases */ public String[] getOrderedSupportedSources() { String[] srcs = this.getSupportedDb(); - ArrayList dassrc = new ArrayList(), - nondas = new ArrayList(); + ArrayList src = new ArrayList<>(); + for (int i = 0; i < srcs.length; i++) { - boolean das = false, skip = false; - String nm; + boolean skip = false; for (DbSourceProxy dbs : getSourceProxy(srcs[i])) { // Skip the alignment databases for the moment - they're not useful for @@ -92,86 +78,28 @@ public class SequenceFetcher extends ASequenceFetcher { skip = true; } - else - { - nm = dbs.getDbName(); - if (getSourceProxy( - srcs[i]) instanceof jalview.ws.dbsources.das.datamodel.DasSequenceSource) - { - if (nm.startsWith("das:")) - { - nm = nm.substring(4); - das = true; - } - break; - } - } } if (skip) { continue; } - if (das) { - dassrc.add(srcs[i]); - } - else - { - nondas.add(srcs[i]); + src.add(srcs[i]); } } - String[] tosort = nondas.toArray(new String[0]), - sorted = nondas.toArray(new String[0]); + String[] tosort = src.toArray(new String[0]), + sorted = src.toArray(new String[0]); for (int j = 0, jSize = sorted.length; j < jSize; j++) { tosort[j] = tosort[j].toLowerCase(); } jalview.util.QuickSort.sort(tosort, sorted); // construct array with all sources listed - - srcs = new String[sorted.length + dassrc.size()]; int i = 0; for (int j = sorted.length - 1; j >= 0; j--, i++) { srcs[i] = sorted[j]; - sorted[j] = null; - } - - sorted = dassrc.toArray(new String[0]); - tosort = dassrc.toArray(new String[0]); - for (int j = 0, jSize = sorted.length; j < jSize; j++) - { - tosort[j] = tosort[j].toLowerCase(); - } - jalview.util.QuickSort.sort(tosort, sorted); - for (int j = sorted.length - 1; j >= 0; j--, i++) - { - srcs[i] = sorted[j]; } return srcs; } - - /** - * query the currently defined DAS source registry for sequence sources and - * add a DasSequenceSource instance for each source to the SequenceFetcher - * source list. - */ - public void registerDasSequenceSources() - { - // TODO: define a context as a registry provider (either desktop, - // jalview.bin.cache, or something else). - for (jalviewSourceI source : jalview.bin.Cache.getDasSourceRegistry() - .getSources()) - { - if (source.isSequenceSource()) - { - List dassources = source.getSequenceSourceProxies(); - for (DbSourceProxy seqsrc : dassources) - { - addDbRefSourceImpl(seqsrc); - } - } - } - } - } diff --git a/src/jalview/ws/dbsources/Pfam.java b/src/jalview/ws/dbsources/Pfam.java index 0227e35..8877c34 100644 --- a/src/jalview/ws/dbsources/Pfam.java +++ b/src/jalview/ws/dbsources/Pfam.java @@ -20,6 +20,7 @@ */ package jalview.ws.dbsources; +import jalview.bin.Cache; import jalview.datamodel.DBRefSource; import com.stevesoft.pat.Regex; @@ -34,6 +35,9 @@ import com.stevesoft.pat.Regex; */ abstract public class Pfam extends Xfam { + static final String PFAM_BASEURL_KEY = "PFAM_BASEURL"; + + private static final String DEFAULT_PFAM_BASEURL = "https://pfam.xfam.org"; public Pfam() { @@ -48,7 +52,6 @@ abstract public class Pfam extends Xfam @Override public String getAccessionSeparator() { - // TODO Auto-generated method stub return null; } @@ -60,7 +63,6 @@ abstract public class Pfam extends Xfam @Override public Regex getAccessionValidator() { - // TODO Auto-generated method stub return null; } @@ -91,17 +93,14 @@ abstract public class Pfam extends Xfam @Override public String getDbVersion() { - // TODO Auto-generated method stub return null; } - /** - * Returns base URL for selected Pfam alignment type - * - * @return PFAM URL stub for this DbSource - */ @Override - protected abstract String getXFAMURL(); + protected String getURLPrefix() + { + return Cache.getDefault(PFAM_BASEURL_KEY, DEFAULT_PFAM_BASEURL); + } /* * (non-Javadoc) diff --git a/src/jalview/ws/dbsources/PfamFull.java b/src/jalview/ws/dbsources/PfamFull.java index ec9fcbb..0600427 100644 --- a/src/jalview/ws/dbsources/PfamFull.java +++ b/src/jalview/ws/dbsources/PfamFull.java @@ -31,20 +31,8 @@ public class PfamFull extends Pfam super(); } - /* - * (non-Javadoc) - * - * @see jalview.ws.dbsources.Pfam#getPFAMURL() - */ - @Override - protected String getXFAMURL() - { - return "http://pfam.xfam.org/family/"; - - } - @Override - public String getXFAMURLSUFFIX() + public String getURLSuffix() { return "/alignment/full"; } diff --git a/src/jalview/ws/dbsources/PfamSeed.java b/src/jalview/ws/dbsources/PfamSeed.java index 33c39b1..dff8a17 100644 --- a/src/jalview/ws/dbsources/PfamSeed.java +++ b/src/jalview/ws/dbsources/PfamSeed.java @@ -33,19 +33,8 @@ public class PfamSeed extends Pfam super(); } - /* - * (non-Javadoc) - * - * @see jalview.ws.dbsources.Pfam#getPFAMURL() - */ - @Override - protected String getXFAMURL() - { - return "http://pfam.xfam.org/family/"; - } - @Override - public String getXFAMURLSUFFIX() + public String getURLSuffix() { return "/alignment/seed"; } diff --git a/src/jalview/ws/dbsources/Rfam.java b/src/jalview/ws/dbsources/Rfam.java index 97f73d0..1d9d99a 100644 --- a/src/jalview/ws/dbsources/Rfam.java +++ b/src/jalview/ws/dbsources/Rfam.java @@ -20,6 +20,7 @@ */ package jalview.ws.dbsources; +import jalview.bin.Cache; import jalview.datamodel.DBRefSource; import com.stevesoft.pat.Regex; @@ -31,6 +32,15 @@ import com.stevesoft.pat.Regex; */ abstract public class Rfam extends Xfam { + static final String RFAM_BASEURL_KEY = "RFAM_BASEURL"; + + private static final String DEFAULT_RFAM_BASEURL = "https://rfam.xfam.org"; + + @Override + protected String getURLPrefix() + { + return Cache.getDefault(RFAM_BASEURL_KEY, DEFAULT_RFAM_BASEURL); + } public Rfam() { @@ -46,7 +56,6 @@ abstract public class Rfam extends Xfam @Override public String getAccessionSeparator() { - // TODO Auto-generated method stub return null; } @@ -58,7 +67,6 @@ abstract public class Rfam extends Xfam @Override public Regex getAccessionValidator() { - // TODO Auto-generated method stub return null; } @@ -82,18 +90,9 @@ abstract public class Rfam extends Xfam @Override public String getDbVersion() { - // TODO Auto-generated method stub return null; } - /** - * Returns base URL for selected Rfam alignment type - * - * @return RFAM URL stub for this DbSource - */ - @Override - protected abstract String getXFAMURL(); - /* * (non-Javadoc) * diff --git a/src/jalview/ws/dbsources/RfamFull.java b/src/jalview/ws/dbsources/RfamFull.java index b2ca31a..d815336 100644 --- a/src/jalview/ws/dbsources/RfamFull.java +++ b/src/jalview/ws/dbsources/RfamFull.java @@ -33,20 +33,8 @@ public class RfamFull extends Rfam super(); } - /* - * (non-Javadoc) - * - * @see jalview.ws.dbsources.Rfam#getXFAMURL() - */ - @Override - protected String getXFAMURL() - { - return "http://rfam.xfam.org/family/"; - - } - @Override - public String getXFAMURLSUFFIX() + public String getURLSuffix() { return "/alignment/full"; } diff --git a/src/jalview/ws/dbsources/RfamSeed.java b/src/jalview/ws/dbsources/RfamSeed.java index f714547..a74e829 100644 --- a/src/jalview/ws/dbsources/RfamSeed.java +++ b/src/jalview/ws/dbsources/RfamSeed.java @@ -33,19 +33,8 @@ public class RfamSeed extends Rfam super(); } - /* - * (non-Javadoc) - * - * @see jalview.ws.dbsources.Rfam#getRFAMURL() - */ - @Override - protected String getXFAMURL() - { - return "http://rfam.xfam.org/family/"; - } - @Override - public String getXFAMURLSUFFIX() + public String getURLSuffix() { // to download gzipped file add '?gzip=1' return "/alignment/stockholm"; diff --git a/src/jalview/ws/dbsources/Uniprot.java b/src/jalview/ws/dbsources/Uniprot.java index 73775cf..167cd97 100644 --- a/src/jalview/ws/dbsources/Uniprot.java +++ b/src/jalview/ws/dbsources/Uniprot.java @@ -20,6 +20,7 @@ */ package jalview.ws.dbsources; +import jalview.bin.Cache; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; @@ -31,6 +32,8 @@ import jalview.datamodel.SequenceI; import jalview.datamodel.xdb.uniprot.UniprotEntry; import jalview.datamodel.xdb.uniprot.UniprotFeature; import jalview.datamodel.xdb.uniprot.UniprotFile; +import jalview.schemes.ResidueProperties; +import jalview.util.StringUtils; import jalview.ws.seqfetcher.DbSourceProxyImpl; import java.io.InputStream; @@ -39,6 +42,7 @@ import java.io.Reader; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; +import java.util.List; import java.util.Vector; import org.exolab.castor.mapping.Mapping; @@ -52,6 +56,8 @@ import com.stevesoft.pat.Regex; */ public class Uniprot extends DbSourceProxyImpl { + private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org"; + private static final String BAR_DELIMITER = "|"; /* @@ -67,6 +73,11 @@ public class Uniprot extends DbSourceProxyImpl super(); } + private String getDomain() + { + return Cache.getDefault("UNIPROT_DOMAIN", DEFAULT_UNIPROT_DOMAIN); + } + /* * (non-Javadoc) * @@ -163,7 +174,7 @@ public class Uniprot extends DbSourceProxyImpl "(UNIPROT\\|?|UNIPROT_|UNIREF\\d+_|UNIREF\\d+\\|?)", ""); AlignmentI al = null; - String downloadstring = "http://www.uniprot.org/uniprot/" + queries + String downloadstring = getDomain() + "/uniprot/" + queries + ".xml"; URL url = null; URLConnection urlconn = null; @@ -270,7 +281,7 @@ public class Uniprot extends DbSourceProxyImpl for (UniprotFeature uf : entry.getFeature()) { SequenceFeature copy = new SequenceFeature(uf.getType(), - uf.getDescription(), uf.getBegin(), uf.getEnd(), "Uniprot"); + getDescription(uf), uf.getBegin(), uf.getEnd(), "Uniprot"); copy.setStatus(uf.getStatus()); sequence.addSequenceFeature(copy); } @@ -283,6 +294,94 @@ public class Uniprot extends DbSourceProxyImpl } /** + * Constructs a feature description from the description and (optionally) + * original and variant fields of the Uniprot XML feature + * + * @param uf + * @return + */ + protected static String getDescription(UniprotFeature uf) + { + String orig = uf.getOriginal(); + List variants = uf.getVariation(); + StringBuilder sb = new StringBuilder(); + + /* + * append variant in standard format if present + * e.g. p.Arg59Lys + * multiple variants are split over lines using
            + */ + boolean asHtml = false; + if (orig != null && !orig.isEmpty() && variants != null + && !variants.isEmpty()) + { + int p = 0; + for (String var : variants) + { + // TODO proper HGVS nomenclature for delins structural variations + // http://varnomen.hgvs.org/recommendations/protein/variant/delins/ + // for now we are pragmatic - any orig/variant sequence longer than + // three characters is shown with single-character notation rather than + // three-letter notation + sb.append("p."); + if (orig.length() < 4) + { + for (int c = 0, clen = orig.length(); c < clen; c++) + { + char origchar = orig.charAt(c); + String orig3 = ResidueProperties.aa2Triplet.get("" + origchar); + sb.append(orig3 == null ? origchar + : StringUtils.toSentenceCase(orig3)); + } + } + else + { + sb.append(orig); + } + + sb.append(Integer.toString(uf.getPosition())); + + if (var.length() < 4) + { + for (int c = 0, clen = var.length(); c < clen; c++) + { + char varchar = var.charAt(c); + String var3 = ResidueProperties.aa2Triplet.get("" + varchar); + + sb.append(var3 != null ? StringUtils.toSentenceCase(var3) + : "" + varchar); + } + } + else + { + sb.append(var); + } + if (++p != variants.size()) + { + sb.append("
              "); + asHtml = true; + } + else + { + sb.append(" "); + } + } + } + String description = uf.getDescription(); + if (description != null) + { + sb.append(description); + } + if (asHtml) + { + sb.insert(0, ""); + sb.append(""); + } + + return sb.toString(); + } + + /** * * @param entry * UniportEntry diff --git a/src/jalview/ws/dbsources/Xfam.java b/src/jalview/ws/dbsources/Xfam.java index 26291eb..b83f558 100644 --- a/src/jalview/ws/dbsources/Xfam.java +++ b/src/jalview/ws/dbsources/Xfam.java @@ -42,7 +42,12 @@ public abstract class Xfam extends DbSourceProxyImpl super(); } - protected abstract String getXFAMURL(); + /** + * the base URL for this Xfam-like service + * + * @return + */ + protected abstract String getURLPrefix(); @Override public abstract String getDbVersion(); @@ -57,8 +62,7 @@ public abstract class Xfam extends DbSourceProxyImpl // retrieved. startQuery(); // TODO: trap HTTP 404 exceptions and return null - String xfamUrl = getXFAMURL() + queries.trim().toUpperCase() - + getXFAMURLSUFFIX(); + String xfamUrl = getURL(queries); if (Cache.log != null) { @@ -83,6 +87,12 @@ public abstract class Xfam extends DbSourceProxyImpl return rcds; } + String getURL(String queries) + { + return getURLPrefix() + "/family/" + queries.trim().toUpperCase() + + getURLSuffix(); + } + /** * Pfam and Rfam provide alignments */ @@ -97,7 +107,7 @@ public abstract class Xfam extends DbSourceProxyImpl * * @return "" for most Xfam sources */ - public String getXFAMURLSUFFIX() + public String getURLSuffix() { return ""; } diff --git a/src/jalview/ws/dbsources/das/api/DasSourceRegistryI.java b/src/jalview/ws/dbsources/das/api/DasSourceRegistryI.java deleted file mode 100644 index 55c50b2..0000000 --- a/src/jalview/ws/dbsources/das/api/DasSourceRegistryI.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.ws.dbsources.das.api; - -import java.util.List; - -import org.biodas.jdas.client.threads.MultipleConnectionPropertyProviderI; - -/** - * API for a registry that provides datasources that jalview can access - * - * @author jprocter - * - */ -public interface DasSourceRegistryI -{ - - List getSources(); - - String getDasRegistryURL(); - - jalviewSourceI getSource(String nickname); - - // TODO: re JAL-424 - introduce form where local source is queried for - // metadata, rather than have it all provided by caller. - jalviewSourceI createLocalSource(String uri, String name, - boolean sequence, boolean features); - - boolean removeLocalSource(jalviewSourceI source); - - void refreshSources(); - - String getLocalSourceString(); - - List resolveSourceNicknames(List sources); - - // TODO: refactor to jDAS specific interface - MultipleConnectionPropertyProviderI getSessionHandler(); -} diff --git a/src/jalview/ws/dbsources/das/api/jalviewSourceI.java b/src/jalview/ws/dbsources/das/api/jalviewSourceI.java deleted file mode 100644 index bc3c8ea..0000000 --- a/src/jalview/ws/dbsources/das/api/jalviewSourceI.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.ws.dbsources.das.api; - -import jalview.ws.seqfetcher.DbSourceProxy; - -import java.util.List; - -import org.biodas.jdas.schema.sources.MAINTAINER; -import org.biodas.jdas.schema.sources.VERSION; - -public interface jalviewSourceI -{ - - String getTitle(); - - VERSION getVersion(); - - String getDocHref(); - - String getDescription(); - - String getUri(); - - MAINTAINER getMAINTAINER(); - - String getEmail(); - - boolean isLocal(); - - boolean isSequenceSource(); - - String[] getCapabilityList(VERSION v); - - String[] getLabelsFor(VERSION v); - - /** - * - * @return null if not a sequence source, otherwise a series of database - * sources that can be used to retrieve sequence data for particular - * database coordinate systems - */ - List getSequenceSourceProxies(); - - boolean isFeatureSource(); - - /** - * returns the base URL for the latest version of a source's DAS endpoint set - * - * @return - */ - String getSourceURL(); - - /** - * test to see if this source's latest version is older than the given source - * - * @param jalviewSourceI - * @return true if newer than given source - */ - boolean isNewerThan(jalviewSourceI jalviewSourceI); - - /** - * test if the source is a reference source for the authority - * - * @return - */ - boolean isReferenceSource(); - -} diff --git a/src/jalview/ws/dbsources/das/datamodel/DasSequenceSource.java b/src/jalview/ws/dbsources/das/datamodel/DasSequenceSource.java deleted file mode 100644 index 84f6d4d..0000000 --- a/src/jalview/ws/dbsources/das/datamodel/DasSequenceSource.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.ws.dbsources.das.datamodel; - -import jalview.bin.Cache; -import jalview.datamodel.Alignment; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.DBRefEntry; -import jalview.datamodel.Sequence; -import jalview.datamodel.SequenceI; -import jalview.util.MessageManager; -import jalview.ws.dbsources.das.api.jalviewSourceI; -import jalview.ws.seqfetcher.DbSourceProxy; -import jalview.ws.seqfetcher.DbSourceProxyImpl; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.Vector; - -import org.biodas.jdas.client.SequenceClient; -import org.biodas.jdas.client.adapters.sequence.DasSequenceAdapter; -import org.biodas.jdas.client.threads.MultipleConnectionPropertyProviderI; -import org.biodas.jdas.client.threads.SequenceClientMultipleSources; -import org.biodas.jdas.schema.sequence.SEQUENCE; -import org.biodas.jdas.schema.sources.COORDINATES; -import org.biodas.jdas.schema.sources.SOURCE; -import org.biodas.jdas.schema.sources.VERSION; - -import com.stevesoft.pat.Regex; - -/** - * an instance of this class is created for each unique DAS Sequence source (ie - * one capable of handling the 'sequence' for a particular MapMaster) - * - * @author JimP - * - */ -public class DasSequenceSource extends DbSourceProxyImpl - implements DbSourceProxy -{ - private jalviewSourceI jsrc; - - protected SOURCE source = null; - - protected VERSION version = null; - - protected COORDINATES coordsys = null; - - protected String dbname = "DASCS"; - - protected String dbrefname = "das:source"; - - protected MultipleConnectionPropertyProviderI connprops = null; - - /** - * DAS sources are tier 1 - if we have a direct DB connection then we should - * prefer it - */ - private int tier = 1; - - /** - * create a new DbSource proxy for a DAS 1 source - * - * @param dbnbame - * Human Readable Name to use when fetching from this source - * @param dbrefname - * DbRefName for DbRefs attached to sequences retrieved from this - * source - * @param source - * Das1Source - * @param coordsys - * specific coordinate system to use for this source - * @throws Exception - * if source is not capable of the 'sequence' command - */ - public DasSequenceSource(String dbname, String dbrefname, SOURCE source, - VERSION version, COORDINATES coordsys, - MultipleConnectionPropertyProviderI connprops) throws Exception - { - if (!(jsrc = new JalviewSource(source, connprops, false)) - .isSequenceSource()) - { - throw new Exception(MessageManager.formatMessage( - "exception.das_source_doesnt_support_sequence_command", - new String[] - { source.getTitle() })); - } - this.tier = 1 + ((jsrc.isLocal() || jsrc.isReferenceSource()) ? 0 : 1); - this.source = source; - this.dbname = dbname; - this.dbrefname = dbrefname.toUpperCase(); - if (coordsys != null) - { - this.coordsys = coordsys; - } - this.connprops = connprops; - } - - public String getAccessionSeparator() - { - return "\t"; - } - - public Regex getAccessionValidator() - { - /** ? * */ - return Regex.perlCode("m/([^:]+)(:\\d+,\\d+)?/"); - } - - public String getDbName() - { - // TODO: map to - return dbname + " (DAS)"; - } - - public String getDbSource() - { - return dbrefname; - } - - public String getDbVersion() - { - return coordsys != null ? coordsys.getVersion() : ""; - } - - public AlignmentI getSequenceRecords(String queries) throws Exception - { - StringTokenizer st = new StringTokenizer(queries, "\t"); - List toks = new ArrayList(), - src = new ArrayList(), acIds = new ArrayList(); - while (st.hasMoreTokens()) - { - String t; - toks.add(t = st.nextToken()); - acIds.add(t.replaceAll(":[0-9,]+", "")); - } - src.add(jsrc.getSourceURL()); - Map, DasSequenceAdapter>> resultset = new HashMap, DasSequenceAdapter>>(); - Map, Exception>> errors = new HashMap, Exception>>(); - - // First try multiple sources - boolean multiple = true, retry = false; - do - { - if (!multiple) - { - retry = false; - // slow, fetch one at a time. - for (String sr : src) - { - System.err.println( - "Retrieving IDs individually from das source: " + sr); - org.biodas.jdas.client.SequenceClient sq = new SequenceClient( - connprops.getConnectionPropertyProviderFor(sr)); - for (String q : toks) - { - List qset = Arrays.asList(new String[] { q }); - try - { - DasSequenceAdapter s = sq.fetchData(sr, qset); - Map, DasSequenceAdapter> dss = resultset.get(sr); - if (dss == null) - { - resultset.put(sr, - dss = new HashMap, DasSequenceAdapter>()); - } - dss.put(qset, s); - } catch (Exception x) - { - Map, Exception> ers = errors.get(sr); - if (ers == null) - { - errors.put(sr, - ers = new HashMap, Exception>()); - } - ers.put(qset, x); - } - } - } - } - else - { - SequenceClientMultipleSources sclient; - sclient = new SequenceClientMultipleSources(); - sclient.fetchData(src, toks, resultset, errors); - sclient.shutDown(); - while (!sclient.isTerminated()) - { - try - { - Thread.sleep(200); - - } catch (InterruptedException x) - { - } - } - if (resultset.isEmpty() && !errors.isEmpty()) - { - retry = true; - multiple = false; - } - } - } while (retry); - - if (resultset.isEmpty()) - { - System.err.println("Sequence Query to " + jsrc.getTitle() + " with '" - + queries + "' returned no sequences."); - return null; - } - else - { - Vector seqs = null; - for (Map.Entry, DasSequenceAdapter>> resset : resultset - .entrySet()) - { - for (Map.Entry, DasSequenceAdapter> result : resset - .getValue().entrySet()) - { - DasSequenceAdapter dasseqresp = result.getValue(); - List accessions = result.getKey(); - for (SEQUENCE e : dasseqresp.getSequence()) - { - String lbl = e.getId(); - - if (acIds.indexOf(lbl) == -1) - { - System.err.println( - "Warning - received sequence event for strange accession code (" - + lbl + ")"); - } - else - { - if (seqs == null) - { - if (e.getContent().length() == 0) - { - System.err.println( - "Empty sequence returned for accession code (" - + lbl + ") from " + resset.getKey() - + " (source is " + getDbName()); - continue; - } - } - seqs = new java.util.Vector(); - // JDAS returns a sequence complete with any newlines and spaces - // in the XML - Sequence sq = new Sequence(lbl, - e.getContent().replaceAll("\\s+", "")); - sq.setStart(e.getStart().intValue()); - sq.addDBRef(new DBRefEntry(getDbSource(), - getDbVersion() + ":" + e.getVersion(), lbl)); - seqs.addElement(sq); - } - } - } - } - - if (seqs == null || seqs.size() == 0) - return null; - SequenceI[] sqs = new SequenceI[seqs.size()]; - for (int i = 0, iSize = seqs.size(); i < iSize; i++) - { - sqs[i] = (SequenceI) seqs.elementAt(i); - } - Alignment al = new Alignment(sqs); - if (jsrc.isFeatureSource()) - { - java.util.Vector srcs = new java.util.Vector(); - srcs.addElement(jsrc); - try - { - jalview.ws.DasSequenceFeatureFetcher dssf = new jalview.ws.DasSequenceFeatureFetcher( - sqs, null, srcs, false, false, multiple); - while (dssf.isRunning()) - { - try - { - Thread.sleep(200); - } catch (InterruptedException x) - { - - } - } - - } catch (Exception x) - { - Cache.log.error( - "Couldn't retrieve features for sequence from its source.", - x); - } - } - - return al; - } - } - - public String getTestQuery() - { - return coordsys == null ? "" : coordsys.getTestRange(); - } - - public boolean isValidReference(String accession) - { - // TODO try to validate an accession against source - // We don't really know how to do this without querying source - - return true; - } - - /** - * @return the source - */ - public SOURCE getSource() - { - return source; - } - - /** - * @return the coordsys - */ - public COORDINATES getCoordsys() - { - return coordsys; - } - - @Override - public int getTier() - { - return tier; - } -} diff --git a/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java b/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java deleted file mode 100644 index de7f380..0000000 --- a/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.ws.dbsources.das.datamodel; - -import jalview.bin.Cache; -import jalview.ws.dbsources.das.api.DasSourceRegistryI; -import jalview.ws.dbsources.das.api.jalviewSourceI; - -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.StringTokenizer; - -import org.biodas.jdas.client.ConnectionPropertyProviderI; -import org.biodas.jdas.client.SourcesClient; -import org.biodas.jdas.client.threads.MultipleConnectionPropertyProviderI; -import org.biodas.jdas.dassources.Capabilities; -import org.biodas.jdas.schema.sources.CAPABILITY; -import org.biodas.jdas.schema.sources.SOURCE; -import org.biodas.jdas.schema.sources.SOURCES; -import org.biodas.jdas.schema.sources.VERSION; - -/** - * - */ -public class DasSourceRegistry - implements DasSourceRegistryI, MultipleConnectionPropertyProviderI -{ - // private org.biodas.jdas.schema.sources.SOURCE[] dasSources = null; - private List dasSources = null; - - private Hashtable sourceNames = null; - - private Hashtable localSources = null; - - // public static String DEFAULT_REGISTRY = "http://www.dasregistry.org/das/"; - public static String DEFAULT_REGISTRY = "http://www.ebi.ac.uk/das-srv/registry/das/"; - - /** - * true if thread is running and we are talking to DAS registry service - */ - private boolean loadingDasSources = false; - - public boolean isLoadingDasSources() - { - return loadingDasSources; - } - - @Override - public String getDasRegistryURL() - { - String registry = jalview.bin.Cache.getDefault("DAS_REGISTRY_URL", - DEFAULT_REGISTRY); - - if (registry.indexOf("/registry/das1/sources/") > -1) - { - jalview.bin.Cache.setProperty(jalview.bin.Cache.DAS_REGISTRY_URL, - DEFAULT_REGISTRY); - registry = DEFAULT_REGISTRY; - } - if (registry.lastIndexOf("sources.xml") == registry.length() - 11) - { - // no trailing sources.xml document for registry in JDAS - jalview.bin.Cache.setProperty(jalview.bin.Cache.DAS_REGISTRY_URL, - registry = registry.substring(0, - registry.lastIndexOf("sources.xml"))); - } - return registry; - } - - /** - * query the default DAS Source Registry for sources. Uses value of jalview - * property DAS_REGISTRY_URL and the DasSourceBrowser.DEFAULT_REGISTRY if that - * doesn't exist. - * - * @return list of sources - */ - private List getDASSources() - { - - return getDASSources(getDasRegistryURL(), this); - } - - /** - * query the given URL for DasSources. - * - * @param registryURL - * return sources from registryURL - */ - private static List getDASSources(String registryURL, - MultipleConnectionPropertyProviderI registry) - { - try - { - URL url = new URL(registryURL); - org.biodas.jdas.client.SourcesClientInterface client = new SourcesClient(); - - SOURCES sources = client.fetchDataRegistry(registryURL, null, null, - null, null, null, null); - - List dassources = sources.getSOURCE(); - ArrayList dsrc = new ArrayList(); - HashMap latests = new HashMap(); - Integer latest; - for (SOURCE src : dassources) - { - JalviewSource jsrc = new JalviewSource(src, registry, false); - latest = latests.get(jsrc.getSourceURL()); - if (latest != null) - { - if (jsrc.isNewerThan(dsrc.get(latest.intValue()))) - { - dsrc.set(latest.intValue(), jsrc); - } - else - { - System.out.println( - "Debug: Ignored older source " + jsrc.getTitle()); - } - } - else - { - latests.put(jsrc.getSourceURL(), Integer.valueOf(dsrc.size())); - dsrc.add(jsrc); - } - } - return dsrc; - } catch (Exception ex) - { - System.out.println( - "DAS1 registry at " + registryURL + " no longer exists"); - return new ArrayList(); - } - } - - public void run() - { - getSources(); - } - - @Override - public List getSources() - { - if (dasSources == null) - { - dasSources = getDASSources(); - } - return appendLocalSources(); - } - - /** - * generate Sources from the local das source list - * - */ - private void addLocalDasSources() - { - if (localSources == null) - { - // get local sources from properties and initialise the local source list - String local = jalview.bin.Cache.getProperty("DAS_LOCAL_SOURCE"); - - if (local != null) - { - int n = 1; - StringTokenizer st = new StringTokenizer(local, "\t"); - while (st.hasMoreTokens()) - { - String token = st.nextToken(); - int bar = token.indexOf("|"); - if (bar == -1) - { - System.err.println( - "Warning: DAS user local source appears to have no nickname (expected a '|' followed by nickname)\nOffending definition: '" - + token + "'"); - } - String url = token.substring(bar + 1); - boolean features = true, sequence = false; - if (url.startsWith("sequence:")) - { - url = url.substring(9); - // this source also serves sequences as well as features - sequence = true; - } - try - { - if (bar > -1) - { - createLocalSource(url, token.substring(0, bar), sequence, - features); - } - else - { - createLocalSource(url, "User Source" + n, sequence, features); - } - } catch (Exception q) - { - System.err.println( - "Unexpected exception when creating local source from '" - + token + "'"); - q.printStackTrace(); - } - n++; - } - } - } - } - - private List appendLocalSources() - { - List srclist = new ArrayList(); - addLocalDasSources(); - sourceNames = new Hashtable(); - if (dasSources != null) - { - for (jalviewSourceI src : dasSources) - { - sourceNames.put(src.getTitle(), src); - srclist.add(src); - } - } - - if (localSources == null) - { - return srclist; - } - Enumeration en = localSources.keys(); - while (en.hasMoreElements()) - { - String key = en.nextElement().toString(); - jalviewSourceI jvsrc = localSources.get(key); - sourceNames.put(key, jvsrc); - srclist.add(jvsrc); - } - return srclist; - } - - /* - * - */ - - @Override - public jalviewSourceI createLocalSource(String url, String name, - boolean sequence, boolean features) - { - SOURCE local = _createLocalSource(url, name, sequence, features); - - if (localSources == null) - { - localSources = new Hashtable(); - } - jalviewSourceI src = new JalviewSource(local, this, true); - localSources.put(local.getTitle(), src); - return src; - } - - private SOURCE _createLocalSource(String url, String name, - boolean sequence, boolean features) - { - SOURCE local = new SOURCE(); - - local.setUri(url); - local.setTitle(name); - local.setVERSION(new ArrayList()); - VERSION v = new VERSION(); - List cp = new ArrayList(); - if (sequence) - { - /* - * Could try and synthesize a coordinate system for the source if needbe - * COORDINATES coord = new COORDINATES(); coord.setAuthority("NCBI"); - * coord.setSource("Chromosome"); coord.setTaxid("9606"); - * coord.setVersion("35"); version.getCOORDINATES().add(coord); - */ - CAPABILITY cap = new CAPABILITY(); - cap.setType("das1:" + Capabilities.SEQUENCE.getName()); - cap.setQueryUri(url + "/sequence"); - cp.add(cap); - } - if (features) - { - CAPABILITY cap = new CAPABILITY(); - cap.setType("das1:" + Capabilities.FEATURES.getName()); - cap.setQueryUri(url + "/features"); - cp.add(cap); - } - - v.getCAPABILITY().addAll(cp); - local.getVERSION().add(v); - - return local; - } - - @Override - public jalviewSourceI getSource(String nickname) - { - return sourceNames.get(nickname); - } - - @Override - public boolean removeLocalSource(jalviewSourceI source) - { - if (localSources.containsValue(source)) - { - localSources.remove(source.getTitle()); - sourceNames.remove(source.getTitle()); - dasSources.remove(source); - jalview.bin.Cache.setProperty("DAS_LOCAL_SOURCE", - getLocalSourceString()); - - return true; - } - return false; - } - - @Override - public void refreshSources() - { - dasSources = null; - sourceNames = null; - run(); - } - - @Override - public List resolveSourceNicknames(List sources) - { - ArrayList resolved = new ArrayList(); - if (sourceNames != null) - { - for (String src : sources) - { - jalviewSourceI dsrc = sourceNames.get(src); - if (dsrc != null) - { - resolved.add(dsrc); - } - } - } - return resolved; - } - - @Override - public String getLocalSourceString() - { - if (localSources != null) - { - StringBuffer sb = new StringBuffer(); - Enumeration en = localSources.keys(); - while (en.hasMoreElements()) - { - String token = en.nextElement().toString(); - jalviewSourceI srco = localSources.get(token); - sb.append(token + "|" + (srco.isSequenceSource() ? "sequence:" : "") - + srco.getUri() + "\t"); - } - return sb.toString(); - } - return ""; - } - - private static final Hashtable authStash; - static - { - authStash = new Hashtable(); - - try - { - // TODO: allow same credentials for https and http - authStash.put( - new URL("http://www.compbio.dundee.ac.uk/geneweb/das/myseq/"), - "Basic SmltOm1pSg=="); - } catch (MalformedURLException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Override - public MultipleConnectionPropertyProviderI getSessionHandler() - { - return this; - } - - @Override - public ConnectionPropertyProviderI getConnectionPropertyProviderFor( - String arg0) - { - - final ConnectionPropertyProviderI conprov = new ConnectionPropertyProviderI() - { - boolean authed = false; - - @Override - public void setConnectionProperties(HttpURLConnection connection) - { - String auth = authStash.get(connection.getURL()); - if (auth != null && auth.length() > 0) - { - connection.setRequestProperty("Authorisation", auth); - authed = true; - } - else - { - authed = false; - } - } - - @Override - public boolean getResponseProperties(HttpURLConnection connection) - { - String auth = authStash.get(connection.getURL()); - if (auth != null && auth.length() == 0) - { - // don't attempt to check if we authed or not - user entered empty - // password - return false; - } - if (!authed) - { - if (auth != null) - { - // try and pass credentials. - return true; - } - // see if we should try and create a new auth record. - String ameth = connection.getHeaderField("X-DAS-AuthMethods"); - Cache.log.debug("Could authenticate to " + connection.getURL() - + " with : " + ameth); - // TODO: search auth string and raise login box - return if auth was - // provided - return false; - } - else - { - // check to see if auth was successful - String asuc = connection - .getHeaderField("X-DAS_AuthenticatedUser"); - if (asuc != null && asuc.trim().length() > 0) - { - // authentication was successful - Cache.log.debug("Authenticated successfully to " - + connection.getURL().toString()); - return false; - } - // it wasn't - so we should tell the user it failed and ask if they - // want to attempt authentication again. - authStash.remove(connection.getURL()); - // open a new login/password dialog with cancel button - // set new authStash content with password and return true - return true; // - // User cancelled auth - so put empty string in stash to indicate we - // don't want to auth with this server. - // authStash.put(connection.getURL(), ""); - // return false; - } - } - }; - return conprov; - } - -} diff --git a/src/jalview/ws/dbsources/das/datamodel/JalviewSource.java b/src/jalview/ws/dbsources/das/datamodel/JalviewSource.java deleted file mode 100644 index 07ba027..0000000 --- a/src/jalview/ws/dbsources/das/datamodel/JalviewSource.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.ws.dbsources.das.datamodel; - -import jalview.util.MessageManager; -import jalview.ws.dbsources.das.api.jalviewSourceI; -import jalview.ws.seqfetcher.DbSourceProxy; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; - -import org.biodas.jdas.client.threads.MultipleConnectionPropertyProviderI; -import org.biodas.jdas.dassources.Capabilities; -import org.biodas.jdas.dassources.utils.DasTimeFormat; -import org.biodas.jdas.schema.sources.CAPABILITY; -import org.biodas.jdas.schema.sources.COORDINATES; -import org.biodas.jdas.schema.sources.MAINTAINER; -import org.biodas.jdas.schema.sources.PROP; -import org.biodas.jdas.schema.sources.SOURCE; -import org.biodas.jdas.schema.sources.VERSION; - -public class JalviewSource implements jalviewSourceI -{ - SOURCE source; - - MultipleConnectionPropertyProviderI connprov; - - public JalviewSource(SOURCE local2, - MultipleConnectionPropertyProviderI connprov, boolean local) - { - this.connprov = connprov; - this.local = local; - source = local2; - } - - @Override - public String getTitle() - { - return source.getTitle(); - } - - @Override - public VERSION getVersion() - { - - return getVersionFor(source); - } - - @Override - public String getDocHref() - { - return source.getDocHref(); - } - - @Override - public String getDescription() - { - return source.getDescription(); - } - - @Override - public String getUri() - { - return source.getUri(); - } - - @Override - public MAINTAINER getMAINTAINER() - { - return source.getMAINTAINER(); - } - - @Override - public String getEmail() - { - return (local) ? null : source.getMAINTAINER().getEmail(); - } - - boolean local = false; - - @Override - public boolean isLocal() - { - return local; - } - - @Override - public boolean isSequenceSource() - { - String seqcap = "das1:" + Capabilities.SEQUENCE.getName(); - for (String cp : getCapabilityList(getVersionFor(source))) - { - if (cp.equals(seqcap)) - { - return true; - - } - } - return false; - } - - @Override - public boolean isFeatureSource() - { - String seqcap = "das1:" + Capabilities.FEATURES.getName(); - for (String cp : getCapabilityList(getVersionFor(source))) - { - if (cp.equals(seqcap)) - { - return true; - - } - } - return false; - } - - private VERSION getVersionFor(SOURCE ds) - { - VERSION latest = null; - for (VERSION v : ds.getVERSION()) - { - if (latest == null - || isLaterThan(latest.getCreated(), v.getCreated())) - { - // TODO: das 1.6 - should just get the first version - ignore other - // versions since not specified how to construct URL from version's URI - // + source URI - latest = v; - } - } - return latest; - } - - /** - * compare date strings. null or unparseable dates are assumed to be oldest - * - * @param ref - * @param newer - * @return true iff ref comes before newer - */ - private boolean isLaterThan(String ref, String newer) - { - Date refdate = null, newdate = null; - if (ref != null && ref.trim().length() > 0) - { - try - { - refdate = DasTimeFormat.fromDASString(ref.trim()); - - } catch (ParseException x) - { - } - } - if (newer != null && newer.trim().length() > 0) - { - try - { - newdate = DasTimeFormat.fromDASString(newer); - } catch (ParseException e) - { - } - } - if (refdate != null) - { - if (newdate != null) - { - return refdate.before(newdate); - } - return false; - } - if (newdate != null) - { - return true; - } - // assume first instance of source is newest in list. - TODO: check if - // natural ordering of source versions is newest first or oldest first - return false; - } - - public String[] getLabelsFor(VERSION v) - { - ArrayList labels = new ArrayList(); - for (PROP p : v.getPROP()) - { - if (p.getName().equalsIgnoreCase("LABEL")) - { - labels.add(p.getValue()); - } - } - return labels.toArray(new String[0]); - } - - private CAPABILITY getCapability(Capabilities capability) - { - for (CAPABILITY p : getVersion().getCAPABILITY()) - { - if (p.getType().equalsIgnoreCase(capability.getName()) || p.getType() - .equalsIgnoreCase("das1:" + capability.getName())) - { - return p; - } - } - return null; - } - - public String[] getCapabilityList(VERSION v) - { - - ArrayList labels = new ArrayList(); - for (CAPABILITY p : v.getCAPABILITY()) - { - // TODO: work out what to do with namespace prefix - // does SEQUENCE == das1:SEQUENCE and das2:SEQUENCE ? - // for moment, just show all capabilities... - if (p.getType().startsWith("das1:")) - { - labels.add(p.getType()); - } - } - return labels.toArray(new String[0]); - } - - @Override - public List getSequenceSourceProxies() - { - if (!isSequenceSource()) - { - return null; - } - ArrayList seqsources = new ArrayList(); - if (!local) - { - VERSION v = getVersion(); - Map latestc = new Hashtable(); - for (COORDINATES cs : v.getCOORDINATES()) - { - COORDINATES ltst = latestc.get(cs.getUri()); - if (ltst == null || ltst.getVersion() == null - || (ltst.getVersion() != null && cs.getVersion() != null - && isLaterThan(ltst.getVersion(), cs.getVersion()))) - { - latestc.put(cs.getUri(), cs); - } - } - for (COORDINATES cs : latestc.values()) - { - DasSequenceSource ds; - /* - * if (css == null || css.length == 0) { // TODO: query das source - * directly to identify coordinate system... or // have to make up a - * coordinate system css = new DasCoordinateSystem[] { new - * DasCoordinateSystem() }; css[0].setName(d1s.getNickname()); - * css[0].setUniqueId(d1s.getNickname()); } for (int c = 0; c < - * css.length; c++) { - */ - try - { - seqsources.add(ds = new DasSequenceSource( - getTitle() + " (" + cs.getAuthority() + " " - + cs.getSource() - + (cs.getVersion() != null ? " " + cs.getVersion() - : "") - + ")", - cs.getAuthority(), source, v, cs, connprov)); - if (seqsources.size() > 1) - { - System.err.println("Added another sequence DB source for " - + getTitle() + " (" + ds.getDbName() + ")"); - } - } catch (Exception e) - { - System.err.println("Ignoring sequence coord system " + cs + " (" - + cs.getContent() + ") for source " + getTitle() - + "- threw exception when constructing fetcher.\n"); - e.printStackTrace(); - } - } - } - else - { - try - { - seqsources.add(new DasSequenceSource(getTitle(), getTitle(), source, - getVersion(), null, connprov)); - } catch (Exception e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - if (seqsources.size() > 1) - { - // sort by name - DbSourceProxy[] tsort = seqsources.toArray(new DasSequenceSource[0]); - String[] nm = new String[tsort.length]; - for (int i = 0; i < nm.length; i++) - { - nm[i] = tsort[i].getDbName().toLowerCase(); - } - jalview.util.QuickSort.sort(nm, tsort); - seqsources.clear(); - for (DbSourceProxy ssrc : tsort) - { - seqsources.add(ssrc); - } - } - return seqsources; - } - - @Override - public String getSourceURL() - { - try - { - // kind of dumb, since - // org.biodas.jdas.dassources.utils.VersionAdapter.getSourceUriFromQueryUri() - // does this, - // but this way, we can access non DAS 1.6 compliant sources (which have - // to have a URL like /das/ and cause a validation exception) - - for (CAPABILITY cap : getVersion().getCAPABILITY()) - { - String capname = cap.getType() - .substring(cap.getType().indexOf(":") + 1); - int p = cap.getQueryUri().lastIndexOf(capname); - if (p < -1) - { - throw new Exception(MessageManager.formatMessage( - "exception.invalid_das_source", new String[] - { source.getUri() })); - } - if (cap.getQueryUri().charAt(p) == '/') - { - p--; - } - return cap.getQueryUri().substring(0, p); - } - } catch (Exception x) - { - System.err.println("Serious: Couldn't get the URL for source " - + source.getTitle()); - x.printStackTrace(); - } - return null; - } - - @Override - public boolean isNewerThan(jalviewSourceI other) - { - return isLaterThan(getVersion().getCreated(), - other.getVersion().getCreated()); - } - - @Override - public boolean isReferenceSource() - { - // TODO check source object for indication that we are the primary for a DAS - // coordinate system - return false; - } -} diff --git a/src/jalview/ws/jws1/JPredThread.java b/src/jalview/ws/jws1/JPredThread.java index a239625..23d9eb0 100644 --- a/src/jalview/ws/jws1/JPredThread.java +++ b/src/jalview/ws/jws1/JPredThread.java @@ -235,8 +235,7 @@ class JPredThread extends JWS1Thread implements WSClientI { // Adjust input view for gaps // propagate insertions into profile - alhidden = HiddenColumns.propagateInsertions(profileseq, al, - input); + alhidden = al.propagateInsertions(profileseq, input); } } } diff --git a/src/jalview/ws/sifts/SiftsClient.java b/src/jalview/ws/sifts/SiftsClient.java index 68af7c3..b5f9653 100644 --- a/src/jalview/ws/sifts/SiftsClient.java +++ b/src/jalview/ws/sifts/SiftsClient.java @@ -92,14 +92,24 @@ public class SiftsClient implements SiftsClientI private CoordinateSys seqCoordSys = CoordinateSys.UNIPROT; + /** + * PDB sequence position to sequence coordinate mapping as derived from SIFTS + * record for the identified SeqCoordSys Used for lift-over from sequence + * derived from PDB (with first extracted PDBRESNUM as 'start' to the sequence + * being annotated with PDB data + */ + private jalview.datamodel.Mapping seqFromPdbMapping; + private static final int BUFFER_SIZE = 4096; - public static final int UNASSIGNED = -1; + public static final int UNASSIGNED = Integer.MIN_VALUE; private static final int PDB_RES_POS = 0; private static final int PDB_ATOM_POS = 1; + private static final int PDBE_POS = 2; + private static final String NOT_OBSERVED = "Not_Observed"; private static final String SIFTS_FTP_BASE_URL = "http://ftp.ebi.ac.uk/pub/databases/msd/sifts/xml/"; @@ -413,6 +423,11 @@ public class SiftsClient implements SiftsClientI public StructureMapping getSiftsStructureMapping(SequenceI seq, String pdbFile, String chain) throws SiftsException { + SequenceI aseq = seq; + while (seq.getDatasetSequence() != null) + { + seq = seq.getDatasetSequence(); + } structId = (chain == null) ? pdbId : pdbId + "|" + chain; System.out.println("Getting SIFTS mapping for " + structId + ": seq " + seq.getName()); @@ -435,8 +450,9 @@ public class SiftsClient implements SiftsClientI HashMap mapping = getGreedyMapping(chain, seq, ps); String mappingOutput = mappingDetails.toString(); - StructureMapping siftsMapping = new StructureMapping(seq, pdbFile, - pdbId, chain, mapping, mappingOutput); + StructureMapping siftsMapping = new StructureMapping(aseq, pdbFile, + pdbId, chain, mapping, mappingOutput, seqFromPdbMapping); + return siftsMapping; } @@ -444,8 +460,8 @@ public class SiftsClient implements SiftsClientI public HashMap getGreedyMapping(String entityId, SequenceI seq, java.io.PrintStream os) throws SiftsException { - List omitNonObserved = new ArrayList(); - int nonObservedShiftIndex = 0; + List omitNonObserved = new ArrayList<>(); + int nonObservedShiftIndex = 0,pdbeNonObserved=0; // System.out.println("Generating mappings for : " + entityId); Entity entity = null; entity = getEntityById(entityId); @@ -476,7 +492,7 @@ public class SiftsClient implements SiftsClientI TreeMap resNumMap = new TreeMap(); List segments = entity.getSegment(); SegmentHelperPojo shp = new SegmentHelperPojo(seq, mapping, resNumMap, - omitNonObserved, nonObservedShiftIndex); + omitNonObserved, nonObservedShiftIndex,pdbeNonObserved); processSegments(segments, shp); try { @@ -498,15 +514,61 @@ public class SiftsClient implements SiftsClientI { throw new SiftsException("SIFTS mapping failed"); } + // also construct a mapping object between the seq-coord sys and the PDB seq's coord sys Integer[] keys = mapping.keySet().toArray(new Integer[0]); Arrays.sort(keys); seqStart = keys[0]; seqEnd = keys[keys.length - 1]; - + List from=new ArrayList<>(),to=new ArrayList<>(); + int[]_cfrom=null,_cto=null; String matchedSeq = originalSeq; - if (seqStart != UNASSIGNED) + if (seqStart != UNASSIGNED) // fixme! seqStart can map to -1 for a pdb sequence that starts <-1 { + for (int seqps:keys) + { + int pdbpos = mapping.get(seqps)[PDBE_POS]; + if (pdbpos == UNASSIGNED) + { + // not correct - pdbpos might be -1, but leave it for now + continue; + } + if (_cfrom==null || seqps!=_cfrom[1]+1) + { + _cfrom = new int[] { seqps,seqps}; + from.add(_cfrom); + _cto = null; // discontinuity + } else { + _cfrom[1]= seqps; + } + if (_cto==null || pdbpos!=1+_cto[1]) + { + _cto = new int[] { pdbpos,pdbpos}; + to.add(_cto); + } else { + _cto[1] = pdbpos; + } + } + _cfrom = new int[from.size() * 2]; + _cto = new int[to.size() * 2]; + int p = 0; + for (int[] range : from) + { + _cfrom[p++] = range[0]; + _cfrom[p++] = range[1]; + } + ; + p = 0; + for (int[] range : to) + { + _cto[p++] = range[0]; + _cto[p++] = range[1]; + } + ; + + seqFromPdbMapping = new jalview.datamodel.Mapping(null, _cto, _cfrom, + 1, + 1); pdbStart = mapping.get(seqStart)[PDB_RES_POS]; pdbEnd = mapping.get(seqEnd)[PDB_RES_POS]; int orignalSeqStart = seq.getStart(); @@ -559,6 +621,8 @@ public class SiftsClient implements SiftsClientI TreeMap resNumMap = shp.getResNumMap(); List omitNonObserved = shp.getOmitNonObserved(); int nonObservedShiftIndex = shp.getNonObservedShiftIndex(); + int pdbeNonObservedCount = shp.getPdbeNonObserved(); + int firstPDBResNum = UNASSIGNED; for (Segment segment : segments) { // System.out.println("Mapping segments : " + segment.getSegId() + "\\"s @@ -566,6 +630,9 @@ public class SiftsClient implements SiftsClientI List residues = segment.getListResidue().getResidue(); for (Residue residue : residues) { + boolean isObserved = isResidueObserved(residue); + int pdbeIndex = getLeadingIntegerValue(residue.getDbResNum(), + UNASSIGNED); int currSeqIndex = UNASSIGNED; List cRefDbs = residue.getCrossRefDb(); CrossRefDb pdbRefDb = null; @@ -574,6 +641,19 @@ public class SiftsClient implements SiftsClientI if (cRefDb.getDbSource().equalsIgnoreCase(DBRefSource.PDB)) { pdbRefDb = cRefDb; + if (firstPDBResNum == UNASSIGNED) + { + firstPDBResNum = getLeadingIntegerValue(cRefDb.getDbResNum(), + UNASSIGNED); + } + else + { + if (isObserved) + { + // after we find the first observed residue we just increment + firstPDBResNum++; + } + } } if (cRefDb.getDbCoordSys().equalsIgnoreCase(seqCoordSys.getName()) && isAccessionMatched(cRefDb.getDbAccessionId())) @@ -586,11 +666,45 @@ public class SiftsClient implements SiftsClientI } } } + if (!isObserved) + { + ++pdbeNonObservedCount; + } + if (seqCoordSys == seqCoordSys.PDB) // FIXME: is seqCoordSys ever PDBe + // ??? + { + // if the sequence has a primary reference to the PDB, then we are + // dealing with a sequence extracted directly from the PDB. In that + // case, numbering is PDBe - non-observed residues + currSeqIndex = seq.getStart() - 1 + pdbeIndex; + } + if (!isObserved) + { + if (seqCoordSys != CoordinateSys.UNIPROT) // FIXME: PDB or PDBe only + // here + { + // mapping to PDB or PDBe so we need to bookkeep for the + // non-observed + // SEQRES positions + omitNonObserved.add(currSeqIndex); + ++nonObservedShiftIndex; + } + } if (currSeqIndex == UNASSIGNED) { + // change in logic - unobserved residues with no currSeqIndex + // corresponding are still counted in both nonObservedShiftIndex and + // pdbeIndex... continue; } - if (currSeqIndex >= seq.getStart() && currSeqIndex <= seq.getEnd()) + // if (currSeqIndex >= seq.getStart() && currSeqIndex <= seqlength) // + // true + // numbering + // is + // not + // up + // to + // seq.getEnd() { int resNum = (pdbRefDb == null) @@ -599,22 +713,18 @@ public class SiftsClient implements SiftsClientI : getLeadingIntegerValue(pdbRefDb.getDbResNum(), UNASSIGNED); - if (isResidueObserved(residue) - || seqCoordSys == CoordinateSys.UNIPROT) + if (isObserved) { char resCharCode = ResidueProperties .getSingleCharacterCode(ResidueProperties .getCanonicalAminoAcid(residue.getDbResName())); resNumMap.put(currSeqIndex, String.valueOf(resCharCode)); + + int[] mappingcols = new int[] { Integer.valueOf(resNum), + UNASSIGNED, isObserved ? firstPDBResNum : UNASSIGNED }; + + mapping.put(currSeqIndex - nonObservedShiftIndex, mappingcols); } - else - { - omitNonObserved.add(currSeqIndex); - ++nonObservedShiftIndex; - } - mapping.put(currSeqIndex - nonObservedShiftIndex, - new int[] - { Integer.valueOf(resNum), UNASSIGNED }); } } } @@ -904,17 +1014,36 @@ public class SiftsClient implements SiftsClientI private int nonObservedShiftIndex; + /** + * count of number of 'not observed' positions in the PDB record's SEQRES + * (total number of residues with coordinates == length(SEQRES) - + * pdbeNonObserved + */ + private int pdbeNonObserved; + public SegmentHelperPojo(SequenceI seq, HashMap mapping, TreeMap resNumMap, - List omitNonObserved, int nonObservedShiftIndex) + List omitNonObserved, int nonObservedShiftIndex, + int pdbeNonObserved) { setSeq(seq); setMapping(mapping); setResNumMap(resNumMap); setOmitNonObserved(omitNonObserved); setNonObservedShiftIndex(nonObservedShiftIndex); + setPdbeNonObserved(pdbeNonObserved); + } + public void setPdbeNonObserved(int pdbeNonObserved2) + { + this.pdbeNonObserved = pdbeNonObserved2; + } + + public int getPdbeNonObserved() + { + return pdbeNonObserved; + } public SequenceI getSeq() { return seq; @@ -964,6 +1093,7 @@ public class SiftsClient implements SiftsClientI { this.nonObservedShiftIndex = nonObservedShiftIndex; } + } @Override diff --git a/src/org/stackoverflowusers/file/WindowsShortcut.java b/src/org/stackoverflowusers/file/WindowsShortcut.java new file mode 100644 index 0000000..671e002 --- /dev/null +++ b/src/org/stackoverflowusers/file/WindowsShortcut.java @@ -0,0 +1,215 @@ +package org.stackoverflowusers.file; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; + +/** + * Represents a Windows shortcut (typically visible to Java only as a '.lnk' file). + * + * Retrieved 2011-09-23 from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775 + * Originally called LnkParser + * + * Written by: (the stack overflow users, obviously!) + * Apache Commons VFS dependency removed by crysxd (why were we using that!?) https://github.com/crysxd + * Headerified, refactored and commented by Code Bling http://stackoverflow.com/users/675721/code-bling + * Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes + * Adapted by Sam Brightman http://stackoverflow.com/users/2492/sam-brightman + * Based on information in 'The Windows Shortcut File Format' by Jesse Hager <jessehager@iname.com> + * And somewhat based on code from the book 'Swing Hacks: Tips and Tools for Killer GUIs' + * by Joshua Marinacci and Chris Adamson + * ISBN: 0-596-00907-0 + * http://www.oreilly.com/catalog/swinghks/ + */ +public class WindowsShortcut +{ + private boolean isDirectory; + private boolean isLocal; + private String real_file; + + /** + * Provides a quick test to see if this could be a valid link ! + * If you try to instantiate a new WindowShortcut and the link is not valid, + * Exceptions may be thrown and Exceptions are extremely slow to generate, + * therefore any code needing to loop through several files should first check this. + * + * @param file the potential link + * @return true if may be a link, false otherwise + * @throws IOException if an IOException is thrown while reading from the file + */ + public static boolean isPotentialValidLink(File file) throws IOException { + final int minimum_length = 0x64; + InputStream fis = new FileInputStream(file); + boolean isPotentiallyValid = false; + try { + isPotentiallyValid = file.isFile() + && file.getName().toLowerCase().endsWith(".lnk") + && fis.available() >= minimum_length + && isMagicPresent(getBytes(fis, 32)); + } finally { + fis.close(); + } + return isPotentiallyValid; + } + + public WindowsShortcut(File file) throws IOException, ParseException { + InputStream in = new FileInputStream(file); + try { + parseLink(getBytes(in)); + } finally { + in.close(); + } + } + + /** + * @return the name of the filesystem object pointed to by this shortcut + */ + public String getRealFilename() { + return real_file; + } + + /** + * Tests if the shortcut points to a local resource. + * @return true if the 'local' bit is set in this shortcut, false otherwise + */ + public boolean isLocal() { + return isLocal; + } + + /** + * Tests if the shortcut points to a directory. + * @return true if the 'directory' bit is set in this shortcut, false otherwise + */ + public boolean isDirectory() { + return isDirectory; + } + + /** + * Gets all the bytes from an InputStream + * @param in the InputStream from which to read bytes + * @return array of all the bytes contained in 'in' + * @throws IOException if an IOException is encountered while reading the data from the InputStream + */ + private static byte[] getBytes(InputStream in) throws IOException { + return getBytes(in, null); + } + + /** + * Gets up to max bytes from an InputStream + * @param in the InputStream from which to read bytes + * @param max maximum number of bytes to read + * @return array of all the bytes contained in 'in' + * @throws IOException if an IOException is encountered while reading the data from the InputStream + */ + private static byte[] getBytes(InputStream in, Integer max) throws IOException { + // read the entire file into a byte buffer + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + byte[] buff = new byte[256]; + while (max == null || max > 0) { + int n = in.read(buff); + if (n == -1) { + break; + } + bout.write(buff, 0, n); + if (max != null) + max -= n; + } + in.close(); + return bout.toByteArray(); + } + + private static boolean isMagicPresent(byte[] link) { + final int magic = 0x0000004C; + final int magic_offset = 0x00; + return link.length >= 32 && bytesToDword(link, magic_offset) == magic; + } + + /** + * Gobbles up link data by parsing it and storing info in member fields + * @param link all the bytes from the .lnk file + */ + private void parseLink(byte[] link) throws ParseException { + try { + if (!isMagicPresent(link)) + throw new ParseException("Invalid shortcut; magic is missing", 0); + + // get the flags byte + byte flags = link[0x14]; + + // get the file attributes byte + final int file_atts_offset = 0x18; + byte file_atts = link[file_atts_offset]; + byte is_dir_mask = (byte)0x10; + if ((file_atts & is_dir_mask) > 0) { + isDirectory = true; + } else { + isDirectory = false; + } + + // if the shell settings are present, skip them + final int shell_offset = 0x4c; + final byte has_shell_mask = (byte)0x01; + int shell_len = 0; + if ((flags & has_shell_mask) > 0) { + // the plus 2 accounts for the length marker itself + shell_len = bytesToWord(link, shell_offset) + 2; + } + + // get to the file settings + int file_start = 0x4c + shell_len; + + final int file_location_info_flag_offset_offset = 0x08; + int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset]; + isLocal = (file_location_info_flag & 2) == 0; + // get the local volume and local system values + //final int localVolumeTable_offset_offset = 0x0C; + final int basename_offset_offset = 0x10; + final int networkVolumeTable_offset_offset = 0x14; + final int finalname_offset_offset = 0x18; + int finalname_offset = link[file_start + finalname_offset_offset] + file_start; + String finalname = getNullDelimitedString(link, finalname_offset); + if (isLocal) { + int basename_offset = link[file_start + basename_offset_offset] + file_start; + String basename = getNullDelimitedString(link, basename_offset); + real_file = basename + finalname; + } else { + int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start; + int shareName_offset_offset = 0x08; + int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset] + + networkVolumeTable_offset; + String shareName = getNullDelimitedString(link, shareName_offset); + real_file = shareName + "\\" + finalname; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParseException("Could not be parsed, probably not a valid WindowsShortcut", 0); + } + } + + private static String getNullDelimitedString(byte[] bytes, int off) { + int len = 0; + // count bytes until the null character (0) + while (true) { + if (bytes[off + len] == 0) { + break; + } + len++; + } + return new String(bytes, off, len); + } + + /* + * convert two bytes into a short note, this is little endian because it's + * for an Intel only OS. + */ + private static int bytesToWord(byte[] bytes, int off) { + return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); + } + + private static int bytesToDword(byte[] bytes, int off) { + return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off); + } + +} diff --git a/test/MCview/PDBChainTest.java b/test/MCview/PDBChainTest.java index defcdbc..533c0af 100644 --- a/test/MCview/PDBChainTest.java +++ b/test/MCview/PDBChainTest.java @@ -91,7 +91,7 @@ public class PDBChainTest a3.resName = "ASP"; a3.resNumber = 41; - Vector v = new Vector(); + Vector v = new Vector<>(); v.add(new Bond(a1, a2)); v.add(new Bond(a2, a3)); v.add(new Bond(a3, a1)); @@ -234,7 +234,7 @@ public class PDBChainTest @Test(groups = { "Functional" }) public void testMakeResidueList_noAnnotation() { - Vector atoms = new Vector(); + Vector atoms = new Vector<>(); c.atoms = atoms; c.isNa = true; atoms.add(makeAtom(4, "N", "MET")); @@ -292,7 +292,7 @@ public class PDBChainTest @Test(groups = { "Functional" }) public void testMakeResidueList_withTempFactor() { - Vector atoms = new Vector(); + Vector atoms = new Vector<>(); c.atoms = atoms; atoms.add(makeAtom(4, "N", "MET")); atoms.get(atoms.size() - 1).tfactor = 1f; @@ -307,7 +307,7 @@ public class PDBChainTest atoms.add(makeAtom(5, "CA", "LYS")); atoms.get(atoms.size() - 1).tfactor = 9f; atoms.add(makeAtom(6, "O", "LEU")); - atoms.get(atoms.size() - 1).tfactor = 4f; + atoms.get(atoms.size() - 1).tfactor = -4f; atoms.add(makeAtom(6, "N", "LEU")); atoms.get(atoms.size() - 1).tfactor = 5f; atoms.add(makeAtom(6, "CA", "LEU")); @@ -320,7 +320,7 @@ public class PDBChainTest /* * Verify annotations; note the tempFactor is read from the first atom in - * each residue i.e. we expect values 1, 7, 4 for the residues + * each residue i.e. we expect values 1, 7, -4 for the residues */ AlignmentAnnotation[] ann = c.sequence.getAnnotation(); assertEquals(1, ann.length); @@ -328,12 +328,12 @@ public class PDBChainTest assertEquals("Temperature Factor for 1gaqA", ann[0].description); assertSame(c.sequence, ann[0].sequenceRef); assertEquals(AlignmentAnnotation.LINE_GRAPH, ann[0].graph); - assertEquals(0f, ann[0].graphMin, 0.001f); + assertEquals(-4f, ann[0].graphMin, 0.001f); assertEquals(7f, ann[0].graphMax, 0.001f); assertEquals(3, ann[0].annotations.length); assertEquals(1f, ann[0].annotations[0].value, 0.001f); assertEquals(7f, ann[0].annotations[1].value, 0.001f); - assertEquals(4f, ann[0].annotations[2].value, 0.001f); + assertEquals(-4f, ann[0].annotations[2].value, 0.001f); } /** @@ -344,7 +344,7 @@ public class PDBChainTest public void testMakeCaBondList() { c.isNa = true; - Vector atoms = new Vector(); + Vector atoms = new Vector<>(); c.atoms = atoms; atoms.add(makeAtom(4, "N", "MET")); atoms.add(makeAtom(4, "CA", "MET")); @@ -375,7 +375,7 @@ public class PDBChainTest public void testMakeCaBondList_nucleotide() { c.isNa = false; - Vector atoms = new Vector(); + Vector atoms = new Vector<>(); c.atoms = atoms; atoms.add(makeAtom(4, "N", "G")); atoms.add(makeAtom(4, "P", "G")); @@ -406,7 +406,7 @@ public class PDBChainTest @Test(groups = { "Functional" }) public void testMakeExactMapping() { - Vector atoms = new Vector(); + Vector atoms = new Vector<>(); c.atoms = atoms; atoms.add(makeAtom(4, "N", "MET")); atoms.add(makeAtom(4, "CA", "MET")); diff --git a/test/jalview/analysis/AlignmentUtilsTests.java b/test/jalview/analysis/AlignmentUtilsTests.java index 06b51e6..a7a7d34 100644 --- a/test/jalview/analysis/AlignmentUtilsTests.java +++ b/test/jalview/analysis/AlignmentUtilsTests.java @@ -34,6 +34,7 @@ import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; import jalview.datamodel.DBRefEntry; +import jalview.datamodel.GeneLociI; import jalview.datamodel.Mapping; import jalview.datamodel.SearchResultMatchI; import jalview.datamodel.SearchResultsI; @@ -47,6 +48,7 @@ import jalview.io.DataSourceType; import jalview.io.FileFormat; import jalview.io.FileFormatI; import jalview.io.FormatAdapter; +import jalview.io.gff.SequenceOntologyI; import jalview.util.MapList; import jalview.util.MappingUtils; @@ -63,6 +65,8 @@ import org.testng.annotations.Test; public class AlignmentUtilsTests { + private static Sequence ts = new Sequence("short", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm"); @BeforeClass(alwaysRun = true) public void setUpJvOptionPane() @@ -71,9 +75,6 @@ public class AlignmentUtilsTests JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); } - public static Sequence ts = new Sequence("short", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm"); - @Test(groups = { "Functional" }) public void testExpandContext() { @@ -263,14 +264,14 @@ public class AlignmentUtilsTests @Test(groups = { "Functional" }) public void testMapProteinAlignmentToCdna_noXrefs() throws IOException { - List protseqs = new ArrayList(); + List protseqs = new ArrayList<>(); protseqs.add(new Sequence("UNIPROT|V12345", "EIQ")); protseqs.add(new Sequence("UNIPROT|V12346", "EIQ")); protseqs.add(new Sequence("UNIPROT|V12347", "SAR")); AlignmentI protein = new Alignment(protseqs.toArray(new SequenceI[3])); protein.setDataset(null); - List dnaseqs = new ArrayList(); + List dnaseqs = new ArrayList<>(); dnaseqs.add(new Sequence("EMBL|A11111", "TCAGCACGC")); // = SAR dnaseqs.add(new Sequence("EMBL|A22222", "GAGATACAA")); // = EIQ dnaseqs.add(new Sequence("EMBL|A33333", "GAAATCCAG")); // = EIQ @@ -507,7 +508,7 @@ public class AlignmentUtilsTests acf.addMap(dna1.getDatasetSequence(), prot1.getDatasetSequence(), map); acf.addMap(dna2.getDatasetSequence(), prot2.getDatasetSequence(), map); acf.addMap(dna3.getDatasetSequence(), prot3.getDatasetSequence(), map); - ArrayList acfs = new ArrayList(); + ArrayList acfs = new ArrayList<>(); acfs.add(acf); protein.setCodonFrames(acfs); @@ -605,14 +606,14 @@ public class AlignmentUtilsTests public void testMapProteinAlignmentToCdna_withStartAndStopCodons() throws IOException { - List protseqs = new ArrayList(); + List protseqs = new ArrayList<>(); protseqs.add(new Sequence("UNIPROT|V12345", "EIQ")); protseqs.add(new Sequence("UNIPROT|V12346", "EIQ")); protseqs.add(new Sequence("UNIPROT|V12347", "SAR")); AlignmentI protein = new Alignment(protseqs.toArray(new SequenceI[3])); protein.setDataset(null); - List dnaseqs = new ArrayList(); + List dnaseqs = new ArrayList<>(); // start + SAR: dnaseqs.add(new Sequence("EMBL|A11111", "ATGTCAGCACGC")); // = EIQ + stop @@ -697,14 +698,14 @@ public class AlignmentUtilsTests @Test(groups = { "Functional" }) public void testMapProteinAlignmentToCdna_withXrefs() throws IOException { - List protseqs = new ArrayList(); + List protseqs = new ArrayList<>(); protseqs.add(new Sequence("UNIPROT|V12345", "EIQ")); protseqs.add(new Sequence("UNIPROT|V12346", "EIQ")); protseqs.add(new Sequence("UNIPROT|V12347", "SAR")); AlignmentI protein = new Alignment(protseqs.toArray(new SequenceI[3])); protein.setDataset(null); - List dnaseqs = new ArrayList(); + List dnaseqs = new ArrayList<>(); dnaseqs.add(new Sequence("EMBL|A11111", "TCAGCACGC")); // = SAR dnaseqs.add(new Sequence("EMBL|A22222", "ATGGAGATACAA")); // = start + EIQ dnaseqs.add(new Sequence("EMBL|A33333", "GAAATCCAG")); // = EIQ @@ -774,14 +775,14 @@ public class AlignmentUtilsTests public void testMapProteinAlignmentToCdna_prioritiseXrefs() throws IOException { - List protseqs = new ArrayList(); + List protseqs = new ArrayList<>(); protseqs.add(new Sequence("UNIPROT|V12345", "EIQ")); protseqs.add(new Sequence("UNIPROT|V12346", "EIQ")); AlignmentI protein = new Alignment( protseqs.toArray(new SequenceI[protseqs.size()])); protein.setDataset(null); - List dnaseqs = new ArrayList(); + List dnaseqs = new ArrayList<>(); dnaseqs.add(new Sequence("EMBL|A11111", "GAAATCCAG")); // = EIQ dnaseqs.add(new Sequence("EMBL|A22222", "GAAATTCAG")); // = EIQ AlignmentI cdna = new Alignment(dnaseqs.toArray(new SequenceI[dnaseqs @@ -848,8 +849,8 @@ public class AlignmentUtilsTests al.addAnnotation(ann4); // Temp for seq1 al.addAnnotation(ann5); // Temp for seq2 al.addAnnotation(ann6); // Temp for no sequence - List types = new ArrayList(); - List scope = new ArrayList(); + List types = new ArrayList<>(); + List scope = new ArrayList<>(); /* * Set all sequence related Structure to hidden (ann1, ann2) @@ -1044,14 +1045,18 @@ public class AlignmentUtilsTests dna.addCodonFrame(acf); /* - * In this case, mappings originally came from matching Uniprot accessions - so need an xref on dna involving those regions. These are normally constructed from CDS annotation + * In this case, mappings originally came from matching Uniprot accessions + * - so need an xref on dna involving those regions. + * These are normally constructed from CDS annotation */ DBRefEntry dna1xref = new DBRefEntry("UNIPROT", "ENSEMBL", "pep1", new Mapping(mapfordna1)); - dna1.getDatasetSequence().addDBRef(dna1xref); + dna1.addDBRef(dna1xref); + assertEquals(2, dna1.getDBRefs().length); // to self and to pep1 DBRefEntry dna2xref = new DBRefEntry("UNIPROT", "ENSEMBL", "pep2", new Mapping(mapfordna2)); - dna2.getDatasetSequence().addDBRef(dna2xref); + dna2.addDBRef(dna2xref); + assertEquals(2, dna2.getDBRefs().length); // to self and to pep2 /* * execute method under test: @@ -1106,6 +1111,38 @@ public class AlignmentUtilsTests assertEquals(cdsMapping.getInverse(), dbref.getMap().getMap()); /* + * verify cDNA has added a dbref with mapping to CDS + */ + assertEquals(3, dna1.getDBRefs().length); + DBRefEntry dbRefEntry = dna1.getDBRefs()[2]; + assertSame(cds1Dss, dbRefEntry.getMap().getTo()); + MapList dnaToCdsMapping = new MapList(new int[] { 4, 6, 10, 12 }, + new int[] { 1, 6 }, 1, 1); + assertEquals(dnaToCdsMapping, dbRefEntry.getMap().getMap()); + assertEquals(3, dna2.getDBRefs().length); + dbRefEntry = dna2.getDBRefs()[2]; + assertSame(cds2Dss, dbRefEntry.getMap().getTo()); + dnaToCdsMapping = new MapList(new int[] { 1, 3, 7, 9, 13, 15 }, + new int[] { 1, 9 }, 1, 1); + assertEquals(dnaToCdsMapping, dbRefEntry.getMap().getMap()); + + /* + * verify CDS has added a dbref with mapping to cDNA + */ + assertEquals(2, cds1Dss.getDBRefs().length); + dbRefEntry = cds1Dss.getDBRefs()[1]; + assertSame(dna1.getDatasetSequence(), dbRefEntry.getMap().getTo()); + MapList cdsToDnaMapping = new MapList(new int[] { 1, 6 }, new int[] { + 4, 6, 10, 12 }, 1, 1); + assertEquals(cdsToDnaMapping, dbRefEntry.getMap().getMap()); + assertEquals(2, cds2Dss.getDBRefs().length); + dbRefEntry = cds2Dss.getDBRefs()[1]; + assertSame(dna2.getDatasetSequence(), dbRefEntry.getMap().getTo()); + cdsToDnaMapping = new MapList(new int[] { 1, 9 }, new int[] { 1, 3, 7, + 9, 13, 15 }, 1, 1); + assertEquals(cdsToDnaMapping, dbRefEntry.getMap().getMap()); + + /* * Verify mappings from CDS to peptide, cDNA to CDS, and cDNA to peptide * the mappings are on the shared alignment dataset * 6 mappings, 2*(DNA->CDS), 2*(DNA->Pep), 2*(CDS->Pep) @@ -1747,7 +1784,7 @@ public class AlignmentUtilsTests map = new MapList(new int[] { 9, 11 }, new int[] { 2, 2 }, 3, 1); acf.addMap(dna3.getDatasetSequence(), prot3.getDatasetSequence(), map); - ArrayList acfs = new ArrayList(); + ArrayList acfs = new ArrayList<>(); acfs.add(acf); protein.setCodonFrames(acfs); @@ -1896,6 +1933,7 @@ public class AlignmentUtilsTests sf6.setValue("alleles", "g, a"); // should force to upper-case sf6.setValue("ID", "sequence_variant:rs758803216"); dna.addSequenceFeature(sf6); + SequenceFeature sf7 = new SequenceFeature("sequence_variant", "", 15, 15, 0f, null); sf7.setValue("alleles", "A, T"); @@ -1985,6 +2023,7 @@ public class AlignmentUtilsTests * variants: * GAA -> E source: Ensembl * CAA -> Q source: dbSNP + * TAA -> STOP source: dnSNP * AAG synonymous source: COSMIC * AAT -> N source: Ensembl * ...TTC synonymous source: dbSNP @@ -2000,39 +2039,51 @@ public class AlignmentUtilsTests String ensembl = "Ensembl"; String dbSnp = "dbSNP"; String cosmic = "COSMIC"; + SequenceFeature sf1 = new SequenceFeature("sequence_variant", "", 1, 1, 0f, ensembl); - sf1.setValue("alleles", "A,G"); // GAA -> E + sf1.setValue("alleles", "A,G"); // AAA -> GAA -> K/E sf1.setValue("ID", "var1.125A>G"); + SequenceFeature sf2 = new SequenceFeature("sequence_variant", "", 1, 1, 0f, dbSnp); - sf2.setValue("alleles", "A,C"); // CAA -> Q + sf2.setValue("alleles", "A,C"); // AAA -> CAA -> K/Q sf2.setValue("ID", "var2"); sf2.setValue("clinical_significance", "Dodgy"); - SequenceFeature sf3 = new SequenceFeature("sequence_variant", "", 3, 3, - 0f, cosmic); - sf3.setValue("alleles", "A,G"); // synonymous + + SequenceFeature sf3 = new SequenceFeature("sequence_variant", "", 1, 1, + 0f, dbSnp); + sf3.setValue("alleles", "A,T"); // AAA -> TAA -> stop codon sf3.setValue("ID", "var3"); - sf3.setValue("clinical_significance", "None"); + sf3.setValue("clinical_significance", "Bad"); + SequenceFeature sf4 = new SequenceFeature("sequence_variant", "", 3, 3, + 0f, cosmic); + sf4.setValue("alleles", "A,G"); // AAA -> AAG synonymous + sf4.setValue("ID", "var4"); + sf4.setValue("clinical_significance", "None"); + + SequenceFeature sf5 = new SequenceFeature("sequence_variant", "", 3, 3, 0f, ensembl); - sf4.setValue("alleles", "A,T"); // AAT -> N - sf4.setValue("ID", "sequence_variant:var4"); // prefix gets stripped off - sf4.setValue("clinical_significance", "Benign"); - SequenceFeature sf5 = new SequenceFeature("sequence_variant", "", 6, 6, + sf5.setValue("alleles", "A,T"); // AAA -> AAT -> K/N + sf5.setValue("ID", "sequence_variant:var5"); // prefix gets stripped off + sf5.setValue("clinical_significance", "Benign"); + + SequenceFeature sf6 = new SequenceFeature("sequence_variant", "", 6, 6, 0f, dbSnp); - sf5.setValue("alleles", "T,C"); // synonymous - sf5.setValue("ID", "var5"); - sf5.setValue("clinical_significance", "Bad"); - SequenceFeature sf6 = new SequenceFeature("sequence_variant", "", 8, 8, - 0f, cosmic); - sf6.setValue("alleles", "C,A,G"); // CAC,CGC -> H,R + sf6.setValue("alleles", "T,C"); // TTT -> TTC synonymous sf6.setValue("ID", "var6"); - sf6.setValue("clinical_significance", "Good"); - List codon1Variants = new ArrayList(); - List codon2Variants = new ArrayList(); - List codon3Variants = new ArrayList(); + SequenceFeature sf7 = new SequenceFeature("sequence_variant", "", 8, 8, + 0f, cosmic); + sf7.setValue("alleles", "C,A,G"); // CCC -> CAC,CGC -> P/H/R + sf7.setValue("ID", "var7"); + sf7.setValue("clinical_significance", "Good"); + + List codon1Variants = new ArrayList<>(); + List codon2Variants = new ArrayList<>(); + List codon3Variants = new ArrayList<>(); + List codonVariants[] = new ArrayList[3]; codonVariants[0] = codon1Variants; codonVariants[1] = codon2Variants; @@ -2043,10 +2094,11 @@ public class AlignmentUtilsTests */ codon1Variants.add(new DnaVariant("A", sf1)); codon1Variants.add(new DnaVariant("A", sf2)); + codon1Variants.add(new DnaVariant("A", sf3)); codon2Variants.add(new DnaVariant("A")); - codon2Variants.add(new DnaVariant("A")); - codon3Variants.add(new DnaVariant("A", sf3)); + // codon2Variants.add(new DnaVariant("A")); codon3Variants.add(new DnaVariant("A", sf4)); + codon3Variants.add(new DnaVariant("A", sf5)); AlignmentUtils.computePeptideVariants(peptide, 1, codonVariants); /* @@ -2057,7 +2109,7 @@ public class AlignmentUtilsTests codon3Variants.clear(); codon1Variants.add(new DnaVariant("T")); codon2Variants.add(new DnaVariant("T")); - codon3Variants.add(new DnaVariant("T", sf5)); + codon3Variants.add(new DnaVariant("T", sf6)); AlignmentUtils.computePeptideVariants(peptide, 2, codonVariants); /* @@ -2067,7 +2119,7 @@ public class AlignmentUtilsTests codon2Variants.clear(); codon3Variants.clear(); codon1Variants.add(new DnaVariant("C")); - codon2Variants.add(new DnaVariant("C", sf6)); + codon2Variants.add(new DnaVariant("C", sf7)); codon3Variants.add(new DnaVariant("C")); AlignmentUtils.computePeptideVariants(peptide, 3, codonVariants); @@ -2075,35 +2127,43 @@ public class AlignmentUtilsTests * verify added sequence features for * var1 K -> E Ensembl * var2 K -> Q dbSNP - * var4 K -> N Ensembl - * var6 P -> H COSMIC - * var6 P -> R COSMIC + * var3 K -> stop + * var4 synonymous + * var5 K -> N Ensembl + * var6 synonymous + * var7 P -> H COSMIC + * var8 P -> R COSMIC */ List sfs = peptide.getSequenceFeatures(); SequenceFeatures.sortFeatures(sfs, true); - assertEquals(5, sfs.size()); + assertEquals(8, sfs.size()); /* * features are sorted by start position ascending, but in no * particular order where start positions match; asserts here * simply match the data returned (the order is not important) */ + // AAA -> AAT -> K/N SequenceFeature sf = sfs.get(0); assertEquals(1, sf.getBegin()); assertEquals(1, sf.getEnd()); + assertEquals("nonsynonymous_variant", sf.getType()); assertEquals("p.Lys1Asn", sf.getDescription()); - assertEquals("var4", sf.getValue("ID")); + assertEquals("var5", sf.getValue("ID")); assertEquals("Benign", sf.getValue("clinical_significance")); - assertEquals("ID=var4;clinical_significance=Benign", sf.getAttributes()); + assertEquals("ID=var5;clinical_significance=Benign", + sf.getAttributes()); assertEquals(1, sf.links.size()); assertEquals( - "p.Lys1Asn var4|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var4", + "p.Lys1Asn var5|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var5", sf.links.get(0)); assertEquals(ensembl, sf.getFeatureGroup()); + // AAA -> CAA -> K/Q sf = sfs.get(1); assertEquals(1, sf.getBegin()); assertEquals(1, sf.getEnd()); + assertEquals("nonsynonymous_variant", sf.getType()); assertEquals("p.Lys1Gln", sf.getDescription()); assertEquals("var2", sf.getValue("ID")); assertEquals("Dodgy", sf.getValue("clinical_significance")); @@ -2114,9 +2174,11 @@ public class AlignmentUtilsTests sf.links.get(0)); assertEquals(dbSnp, sf.getFeatureGroup()); + // AAA -> GAA -> K/E sf = sfs.get(2); assertEquals(1, sf.getBegin()); assertEquals(1, sf.getEnd()); + assertEquals("nonsynonymous_variant", sf.getType()); assertEquals("p.Lys1Glu", sf.getDescription()); assertEquals("var1.125A>G", sf.getValue("ID")); assertNull(sf.getValue("clinical_significance")); @@ -2128,30 +2190,79 @@ public class AlignmentUtilsTests sf.links.get(0)); assertEquals(ensembl, sf.getFeatureGroup()); + // AAA -> TAA -> stop codon sf = sfs.get(3); + assertEquals(1, sf.getBegin()); + assertEquals(1, sf.getEnd()); + assertEquals("stop_gained", sf.getType()); + assertEquals("Aaa/Taa", sf.getDescription()); + assertEquals("var3", sf.getValue("ID")); + assertEquals("Bad", sf.getValue("clinical_significance")); + assertEquals("ID=var3;clinical_significance=Bad", sf.getAttributes()); + assertEquals(1, sf.links.size()); + assertEquals( + "Aaa/Taa var3|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var3", + sf.links.get(0)); + assertEquals(dbSnp, sf.getFeatureGroup()); + + // AAA -> AAG synonymous + sf = sfs.get(4); + assertEquals(1, sf.getBegin()); + assertEquals(1, sf.getEnd()); + assertEquals("synonymous_variant", sf.getType()); + assertEquals("aaA/aaG", sf.getDescription()); + assertEquals("var4", sf.getValue("ID")); + assertEquals("None", sf.getValue("clinical_significance")); + assertEquals("ID=var4;clinical_significance=None", sf.getAttributes()); + assertEquals(1, sf.links.size()); + assertEquals( + "aaA/aaG var4|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var4", + sf.links.get(0)); + assertEquals(cosmic, sf.getFeatureGroup()); + + // TTT -> TTC synonymous + sf = sfs.get(5); + assertEquals(2, sf.getBegin()); + assertEquals(2, sf.getEnd()); + assertEquals("synonymous_variant", sf.getType()); + assertEquals("ttT/ttC", sf.getDescription()); + assertEquals("var6", sf.getValue("ID")); + assertNull(sf.getValue("clinical_significance")); + assertEquals("ID=var6", sf.getAttributes()); + assertEquals(1, sf.links.size()); + assertEquals( + "ttT/ttC var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6", + sf.links.get(0)); + assertEquals(dbSnp, sf.getFeatureGroup()); + + // var7 generates two distinct protein variant features (two alleles) + // CCC -> CGC -> P/R + sf = sfs.get(6); assertEquals(3, sf.getBegin()); assertEquals(3, sf.getEnd()); + assertEquals("nonsynonymous_variant", sf.getType()); assertEquals("p.Pro3Arg", sf.getDescription()); - assertEquals("var6", sf.getValue("ID")); + assertEquals("var7", sf.getValue("ID")); assertEquals("Good", sf.getValue("clinical_significance")); - assertEquals("ID=var6;clinical_significance=Good", sf.getAttributes()); + assertEquals("ID=var7;clinical_significance=Good", sf.getAttributes()); assertEquals(1, sf.links.size()); assertEquals( - "p.Pro3Arg var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6", + "p.Pro3Arg var7|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var7", sf.links.get(0)); assertEquals(cosmic, sf.getFeatureGroup()); - // var5 generates two distinct protein variant features - sf = sfs.get(4); + // CCC -> CAC -> P/H + sf = sfs.get(7); assertEquals(3, sf.getBegin()); assertEquals(3, sf.getEnd()); + assertEquals("nonsynonymous_variant", sf.getType()); assertEquals("p.Pro3His", sf.getDescription()); - assertEquals("var6", sf.getValue("ID")); + assertEquals("var7", sf.getValue("ID")); assertEquals("Good", sf.getValue("clinical_significance")); - assertEquals("ID=var6;clinical_significance=Good", sf.getAttributes()); + assertEquals("ID=var7;clinical_significance=Good", sf.getAttributes()); assertEquals(1, sf.links.size()); assertEquals( - "p.Pro3His var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6", + "p.Pro3His var7|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var7", sf.links.get(0)); assertEquals(cosmic, sf.getFeatureGroup()); } @@ -2272,7 +2383,7 @@ public class AlignmentUtilsTests seq1.createDatasetSequence(); Mapping mapping = new Mapping(seq1, new MapList( new int[] { 3, 6, 9, 10 }, new int[] { 1, 6 }, 1, 1)); - Map> map = new TreeMap>(); + Map> map = new TreeMap<>(); AlignmentUtils.addMappedPositions(seq1, from, mapping, map); /* @@ -2304,7 +2415,7 @@ public class AlignmentUtilsTests seq1.createDatasetSequence(); Mapping mapping = new Mapping(seq1, new MapList( new int[] { 3, 6, 9, 10 }, new int[] { 1, 6 }, 1, 1)); - Map> map = new TreeMap>(); + Map> map = new TreeMap<>(); AlignmentUtils.addMappedPositions(seq1, from, mapping, map); /* @@ -2533,6 +2644,70 @@ public class AlignmentUtilsTests assertEquals(s_as3, uas3.getSequenceAsString()); } + @Test(groups = { "Functional" }) + public void testTransferGeneLoci() + { + SequenceI from = new Sequence("transcript", + "aaacccgggTTTAAACCCGGGtttaaacccgggttt"); + SequenceI to = new Sequence("CDS", "TTTAAACCCGGG"); + MapList map = new MapList(new int[] { 1, 12 }, new int[] { 10, 21 }, 1, + 1); + + /* + * first with nothing to transfer + */ + AlignmentUtils.transferGeneLoci(from, map, to); + assertNull(to.getGeneLoci()); + + /* + * next with gene loci set on 'from' sequence + */ + int[] exons = new int[] { 100, 105, 155, 164, 210, 229 }; + MapList geneMap = new MapList(new int[] { 1, 36 }, exons, 1, 1); + from.setGeneLoci("human", "GRCh38", "7", geneMap); + AlignmentUtils.transferGeneLoci(from, map, to); + + GeneLociI toLoci = to.getGeneLoci(); + assertNotNull(toLoci); + // DBRefEntry constructor upper-cases 'source' + assertEquals("HUMAN", toLoci.getSpeciesId()); + assertEquals("GRCh38", toLoci.getAssemblyId()); + assertEquals("7", toLoci.getChromosomeId()); + + /* + * transcript 'exons' are 1-6, 7-16, 17-36 + * CDS 1:12 is transcript 10-21 + * transcript 'CDS' is 10-16, 17-21 + * which is 'gene' 158-164, 210-214 + */ + MapList toMap = toLoci.getMap(); + assertEquals(1, toMap.getFromRanges().size()); + assertEquals(2, toMap.getFromRanges().get(0).length); + assertEquals(1, toMap.getFromRanges().get(0)[0]); + assertEquals(12, toMap.getFromRanges().get(0)[1]); + assertEquals(2, toMap.getToRanges().size()); + assertEquals(2, toMap.getToRanges().get(0).length); + assertEquals(158, toMap.getToRanges().get(0)[0]); + assertEquals(164, toMap.getToRanges().get(0)[1]); + assertEquals(210, toMap.getToRanges().get(1)[0]); + assertEquals(214, toMap.getToRanges().get(1)[1]); + // or summarised as (but toString might change in future): + assertEquals("[ [1, 12] ] 1:1 to [ [158, 164] [210, 214] ]", + toMap.toString()); + + /* + * an existing value is not overridden + */ + geneMap = new MapList(new int[] { 1, 36 }, new int[] { 36, 1 }, 1, 1); + from.setGeneLoci("inhuman", "GRCh37", "6", geneMap); + AlignmentUtils.transferGeneLoci(from, map, to); + assertEquals("GRCh38", toLoci.getAssemblyId()); + assertEquals("7", toLoci.getChromosomeId()); + toMap = toLoci.getMap(); + assertEquals("[ [1, 12] ] 1:1 to [ [158, 164] [210, 214] ]", + toMap.toString()); + } + /** * Tests for the method that maps nucleotide to protein based on CDS features */ @@ -2561,7 +2736,7 @@ public class AlignmentUtilsTests * Case 2: CDS 3 times length of peptide + stop codon * (note code does not currently check trailing codon is a stop codon) */ - dna = new Sequence("dna", "AACGacgtCTCCTTGA"); + dna = new Sequence("dna", "AACGacgtCTCCTCCC"); dna.createDatasetSequence(); dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null)); dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 16, null)); @@ -2574,17 +2749,42 @@ public class AlignmentUtilsTests Arrays.deepToString(ml.getFromRanges().toArray())); /* - * Case 3: CDS not 3 times length of peptide - no mapping is made + * Case 3: CDS longer than 3 * peptide + stop codon - no mapping is made + */ + dna = new Sequence("dna", "AACGacgtCTCCTTGATCA"); + dna.createDatasetSequence(); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null)); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 19, null)); + ml = AlignmentUtils.mapCdsToProtein(dna, peptide); + assertNull(ml); + + /* + * Case 4: CDS shorter than 3 * peptide - no mapping is made + */ + dna = new Sequence("dna", "AACGacgtCTCC"); + dna.createDatasetSequence(); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null)); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 12, null)); + ml = AlignmentUtils.mapCdsToProtein(dna, peptide); + assertNull(ml); + + /* + * Case 5: CDS 3 times length of peptide + part codon - mapping is truncated */ dna = new Sequence("dna", "AACGacgtCTCCTTG"); dna.createDatasetSequence(); dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null)); dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 15, null)); ml = AlignmentUtils.mapCdsToProtein(dna, peptide); - assertNull(ml); + assertEquals(3, ml.getFromRatio()); + assertEquals(1, ml.getToRatio()); + assertEquals("[[1, 3]]", + Arrays.deepToString(ml.getToRanges().toArray())); + assertEquals("[[1, 4], [9, 13]]", + Arrays.deepToString(ml.getFromRanges().toArray())); /* - * Case 4: incomplete start codon corresponding to X in peptide + * Case 6: incomplete start codon corresponding to X in peptide */ dna = new Sequence("dna", "ACGacgtCTCCTTGG"); dna.createDatasetSequence(); @@ -2600,4 +2800,151 @@ public class AlignmentUtilsTests Arrays.deepToString(ml.getFromRanges().toArray())); } + /** + * Tests for the method that locates the CDS sequence that has a mapping to + * the given protein. That is, given a transcript-to-peptide mapping, find the + * cds-to-peptide mapping that relates to both, and return the CDS sequence. + */ + @Test + public void testFindCdsForProtein() + { + List mappings = new ArrayList<>(); + AlignedCodonFrame acf1 = new AlignedCodonFrame(); + mappings.add(acf1); + + SequenceI dna1 = new Sequence("dna1", "cgatATcgGCTATCTATGacg"); + dna1.createDatasetSequence(); + + // NB we currently exclude STOP codon from CDS sequences + // the test would need to change if this changes in future + SequenceI cds1 = new Sequence("cds1", "ATGCTATCT"); + cds1.createDatasetSequence(); + + SequenceI pep1 = new Sequence("pep1", "MLS"); + pep1.createDatasetSequence(); + List seqMappings = new ArrayList<>(); + MapList mapList = new MapList( + new int[] + { 5, 6, 9, 15 }, new int[] { 1, 3 }, 3, 1); + Mapping dnaToPeptide = new Mapping(pep1.getDatasetSequence(), mapList); + + // add dna to peptide mapping + seqMappings.add(acf1); + acf1.addMap(dna1.getDatasetSequence(), pep1.getDatasetSequence(), + mapList); + + /* + * first case - no dna-to-CDS mapping exists - search fails + */ + SequenceI seq = AlignmentUtils.findCdsForProtein(mappings, dna1, + seqMappings, dnaToPeptide); + assertNull(seq); + + /* + * second case - CDS-to-peptide mapping exists but no dna-to-CDS + * - search fails + */ + // todo this test fails if the mapping is added to acf1, not acf2 + // need to tidy up use of lists of mappings in AlignedCodonFrame + AlignedCodonFrame acf2 = new AlignedCodonFrame(); + mappings.add(acf2); + MapList cdsToPeptideMapping = new MapList(new int[] + { 1, 9 }, new int[] { 1, 3 }, 3, 1); + acf2.addMap(cds1.getDatasetSequence(), pep1.getDatasetSequence(), + cdsToPeptideMapping); + assertNull(AlignmentUtils.findCdsForProtein(mappings, dna1, seqMappings, + dnaToPeptide)); + + /* + * third case - add dna-to-CDS mapping - CDS is now found! + */ + MapList dnaToCdsMapping = new MapList(new int[] { 5, 6, 9, 15 }, + new int[] + { 1, 9 }, 1, 1); + acf1.addMap(dna1.getDatasetSequence(), cds1.getDatasetSequence(), + dnaToCdsMapping); + seq = AlignmentUtils.findCdsForProtein(mappings, dna1, seqMappings, + dnaToPeptide); + assertSame(seq, cds1.getDatasetSequence()); + } + + /** + * Tests for the method that locates the CDS sequence that has a mapping to + * the given protein. That is, given a transcript-to-peptide mapping, find the + * cds-to-peptide mapping that relates to both, and return the CDS sequence. + * This test is for the case where transcript and CDS are the same length. + */ + @Test + public void testFindCdsForProtein_noUTR() + { + List mappings = new ArrayList<>(); + AlignedCodonFrame acf1 = new AlignedCodonFrame(); + mappings.add(acf1); + + SequenceI dna1 = new Sequence("dna1", "ATGCTATCTTAA"); + dna1.createDatasetSequence(); + + // NB we currently exclude STOP codon from CDS sequences + // the test would need to change if this changes in future + SequenceI cds1 = new Sequence("cds1", "ATGCTATCT"); + cds1.createDatasetSequence(); + + SequenceI pep1 = new Sequence("pep1", "MLS"); + pep1.createDatasetSequence(); + List seqMappings = new ArrayList<>(); + MapList mapList = new MapList( + new int[] + { 1, 9 }, new int[] { 1, 3 }, 3, 1); + Mapping dnaToPeptide = new Mapping(pep1.getDatasetSequence(), mapList); + + // add dna to peptide mapping + seqMappings.add(acf1); + acf1.addMap(dna1.getDatasetSequence(), pep1.getDatasetSequence(), + mapList); + + /* + * first case - transcript lacks CDS features - it appears to be + * the CDS sequence and is returned + */ + SequenceI seq = AlignmentUtils.findCdsForProtein(mappings, dna1, + seqMappings, dnaToPeptide); + assertSame(seq, dna1.getDatasetSequence()); + + /* + * second case - transcript has CDS feature - this means it is + * not returned as a match for CDS (CDS sequences don't have CDS features) + */ + dna1.addSequenceFeature( + new SequenceFeature(SequenceOntologyI.CDS, "cds", 1, 12, null)); + seq = AlignmentUtils.findCdsForProtein(mappings, dna1, seqMappings, + dnaToPeptide); + assertNull(seq); + + /* + * third case - CDS-to-peptide mapping exists but no dna-to-CDS + * - search fails + */ + // todo this test fails if the mapping is added to acf1, not acf2 + // need to tidy up use of lists of mappings in AlignedCodonFrame + AlignedCodonFrame acf2 = new AlignedCodonFrame(); + mappings.add(acf2); + MapList cdsToPeptideMapping = new MapList(new int[] + { 1, 9 }, new int[] { 1, 3 }, 3, 1); + acf2.addMap(cds1.getDatasetSequence(), pep1.getDatasetSequence(), + cdsToPeptideMapping); + assertNull(AlignmentUtils.findCdsForProtein(mappings, dna1, seqMappings, + dnaToPeptide)); + + /* + * fourth case - add dna-to-CDS mapping - CDS is now found! + */ + MapList dnaToCdsMapping = new MapList(new int[] { 1, 9 }, + new int[] + { 1, 9 }, 1, 1); + acf1.addMap(dna1.getDatasetSequence(), cds1.getDatasetSequence(), + dnaToCdsMapping); + seq = AlignmentUtils.findCdsForProtein(mappings, dna1, seqMappings, + dnaToPeptide); + assertSame(seq, cds1.getDatasetSequence()); + } } diff --git a/test/jalview/analysis/CrossRefTest.java b/test/jalview/analysis/CrossRefTest.java index 95be1ff..b3c78be 100644 --- a/test/jalview/analysis/CrossRefTest.java +++ b/test/jalview/analysis/CrossRefTest.java @@ -106,7 +106,7 @@ public class CrossRefTest public void testFindXrefSourcesForSequence_proteinToDna() { SequenceI seq = new Sequence("Seq1", "MGKYQARLSS"); - List sources = new ArrayList(); + List sources = new ArrayList<>(); AlignmentI al = new Alignment(new SequenceI[] {}); /* @@ -132,8 +132,9 @@ public class CrossRefTest sources = new CrossRef(new SequenceI[] { seq }, al) .findXrefSourcesForSequences(false); // method is patched to remove EMBL from the sources to match - assertEquals(3, sources.size()); - assertEquals("[EMBLCDS, GENEDB, ENSEMBL]", sources.toString()); + assertEquals(4, sources.size()); + assertEquals("[EMBLCDS, GENEDB, ENSEMBL, ENSEMBLGENOMES]", + sources.toString()); /* * add a sequence to the alignment which has a dbref to UNIPROT|A1234 @@ -270,7 +271,7 @@ public class CrossRefTest pep1.addDBRef(new DBRefEntry("UNIPROT", "0", "Q9ZTS2")); AlignmentI al = new Alignment(new SequenceI[] { dna1, pep1 }); - List result = new ArrayList(); + List result = new ArrayList<>(); /* * first search for a dbref nowhere on the alignment: @@ -426,7 +427,7 @@ public class CrossRefTest * argument false suppresses adding DAS sources * todo: define an interface type SequenceFetcherI and mock that */ - SequenceFetcher mockFetcher = new SequenceFetcher(false) + SequenceFetcher mockFetcher = new SequenceFetcher() { @Override public boolean isFetchable(String source) @@ -504,7 +505,7 @@ public class CrossRefTest * argument false suppresses adding DAS sources * todo: define an interface type SequenceFetcherI and mock that */ - SequenceFetcher mockFetcher = new SequenceFetcher(false) + SequenceFetcher mockFetcher = new SequenceFetcher() { @Override public boolean isFetchable(String source) @@ -650,7 +651,7 @@ public class CrossRefTest * passed in calls to getSequences() - important to verify that * duplicate sequence fetches are not requested */ - SequenceFetcher mockFetcher = new SequenceFetcher(false) + SequenceFetcher mockFetcher = new SequenceFetcher() { int call = 0; diff --git a/test/jalview/analysis/DnaTest.java b/test/jalview/analysis/DnaTest.java index d2fa99a..6a31b31 100644 --- a/test/jalview/analysis/DnaTest.java +++ b/test/jalview/analysis/DnaTest.java @@ -38,6 +38,7 @@ import jalview.io.FileFormat; import jalview.io.FormatAdapter; import java.io.IOException; +import java.util.Iterator; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -135,7 +136,9 @@ public class DnaTest FileFormat.Fasta); HiddenColumns cs = new HiddenColumns(); AlignViewportI av = new AlignViewport(alf, cs); - Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 }); + Iterator contigs = cs.getVisContigsIterator(0, alf.getWidth(), + false); + Dna dna = new Dna(av, contigs); AlignmentI translated = dna.translateCdna(); assertNotNull("Couldn't do a full width translation of test data.", translated); @@ -163,7 +166,8 @@ public class DnaTest cs.hideColumns(0, ipos - 1); } cs.hideColumns(ipos + vwidth, alf.getWidth()); - int[] vcontigs = cs.getVisibleContigs(0, alf.getWidth()); + Iterator vcontigs = cs.getVisContigsIterator(0, + alf.getWidth(), false); AlignViewportI av = new AlignViewport(alf, cs); Dna dna = new Dna(av, vcontigs); AlignmentI transAlf = dna.translateCdna(); @@ -190,7 +194,9 @@ public class DnaTest DataSourceType.PASTE, FileFormat.Fasta); HiddenColumns cs = new HiddenColumns(); AlignViewportI av = new AlignViewport(alf, cs); - Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 }); + Iterator contigs = cs.getVisContigsIterator(0, alf.getWidth(), + false); + Dna dna = new Dna(av, contigs); AlignmentI translated = dna.translateCdna(); String aa = translated.getSequenceAt(0).getSequenceAsString(); assertEquals( @@ -213,7 +219,9 @@ public class DnaTest cs.hideColumns(24, 35); // hide codons 9-12 cs.hideColumns(177, 191); // hide codons 60-64 AlignViewportI av = new AlignViewport(alf, cs); - Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 }); + Iterator contigs = cs.getVisContigsIterator(0, alf.getWidth(), + false); + Dna dna = new Dna(av, contigs); AlignmentI translated = dna.translateCdna(); String aa = translated.getSequenceAt(0).getSequenceAsString(); assertEquals("AACDDGGGGHHIIIKKLLLLLLMNNPPPPQQRRRRRRSSSSSSTTTTVVVVW", aa); @@ -298,7 +306,9 @@ public class DnaTest .generate(12, 8, 97, 5, 5); HiddenColumns cs = new HiddenColumns(); AlignViewportI av = new AlignViewport(cdna, cs); - Dna dna = new Dna(av, new int[] { 0, cdna.getWidth() - 1 }); + Iterator contigs = cs.getVisContigsIterator(0, cdna.getWidth(), + false); + Dna dna = new Dna(av, contigs); AlignmentI translated = dna.translateCdna(); /* @@ -313,7 +323,8 @@ public class DnaTest } AlignmentI cdnaReordered = new Alignment(sorted); av = new AlignViewport(cdnaReordered, cs); - dna = new Dna(av, new int[] { 0, cdna.getWidth() - 1 }); + contigs = cs.getVisContigsIterator(0, cdna.getWidth(), false); + dna = new Dna(av, contigs); AlignmentI translated2 = dna.translateCdna(); /* @@ -544,7 +555,9 @@ public class DnaTest HiddenColumns cs = new HiddenColumns(); AlignViewportI av = new AlignViewport(al, cs); - Dna testee = new Dna(av, new int[] { 0, al.getWidth() - 1 }); + Iterator contigs = cs.getVisContigsIterator(0, al.getWidth(), + false); + Dna testee = new Dna(av, contigs); AlignmentI reversed = testee.reverseCdna(false); assertEquals(1, reversed.getHeight()); assertEquals(seqRev, reversed.getSequenceAt(0).getSequenceAsString()); diff --git a/test/jalview/bin/CommandLineOperations.java b/test/jalview/bin/CommandLineOperations.java index 69a3ef7..3ac8656 100644 --- a/test/jalview/bin/CommandLineOperations.java +++ b/test/jalview/bin/CommandLineOperations.java @@ -35,6 +35,9 @@ import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ScanResult; + public class CommandLineOperations { @@ -45,7 +48,7 @@ public class CommandLineOperations JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); } - private static final int TEST_TIMEOUT = 4500; // Note longer timeout needed on + private static final int TEST_TIMEOUT = 9000; // Note longer timeout needed on // full test run than on // individual tests @@ -55,7 +58,7 @@ public class CommandLineOperations private static final int MINFILESIZE_BIG = 4096; - private ArrayList successfulCMDs = new ArrayList(); + private ArrayList successfulCMDs = new ArrayList<>(); /*** * from @@ -113,13 +116,39 @@ public class CommandLineOperations } } + private static ClassGraph scanner = null; + + private static String classpath = null; + + public synchronized static String getClassPath() + { + if (scanner == null) + { + scanner = new ClassGraph(); + ScanResult scan = scanner.scan(); + classpath = scan.getClasspath(); + } + while (classpath == null) + { + try + { + Thread.sleep(10); + } catch (InterruptedException x) + { + + } + } + return classpath; + } private Worker jalviewDesktopRunner(boolean withAwt, String cmd, int timeout) { + // Note: JAL-3065 - don't include quotes for lib/* because the arguments are + // not expanded by the shell + String classpath = getClassPath(); String _cmd = "java " + (withAwt ? "-Djava.awt.headless=true" : "") - + " -Djava.ext.dirs=./lib -classpath ./classes jalview.bin.Jalview "; - System.out.println("CMD [" + cmd + "]"); + + " -classpath " + classpath + " jalview.bin.Jalview "; Process ls2_proc = null; Worker worker = null; try diff --git a/test/jalview/controller/AlignViewControllerTest.java b/test/jalview/controller/AlignViewControllerTest.java index 2e89b0e..efee93b 100644 --- a/test/jalview/controller/AlignViewControllerTest.java +++ b/test/jalview/controller/AlignViewControllerTest.java @@ -25,6 +25,8 @@ import static org.testng.AssertJUnit.assertTrue; import jalview.analysis.Finder; import jalview.api.AlignViewControllerI; +import jalview.api.FeatureColourI; +import jalview.datamodel.Alignment; import jalview.datamodel.SearchResults; import jalview.datamodel.SearchResultsI; import jalview.datamodel.Sequence; @@ -35,7 +37,9 @@ import jalview.gui.AlignFrame; import jalview.gui.JvOptionPane; import jalview.io.DataSourceType; import jalview.io.FileLoader; +import jalview.schemes.FeatureColour; +import java.awt.Color; import java.util.Arrays; import java.util.BitSet; @@ -67,13 +71,14 @@ public class AlignViewControllerTest null)); seq1.addSequenceFeature(new SequenceFeature("Helix", "desc", 1, 15, 0f, null)); - seq2.addSequenceFeature(new SequenceFeature("Metal", "desc", 4, 10, 0f, + seq2.addSequenceFeature(new SequenceFeature("Metal", "desc", 4, 10, + 10f, null)); seq3.addSequenceFeature(new SequenceFeature("Metal", "desc", 11, 15, - 0f, null)); + 10f, null)); // disulfide bond is a 'contact feature' - only select its 'start' and 'end' - seq3.addSequenceFeature(new SequenceFeature("disulfide bond", "desc", 8, 12, - 0f, null)); + seq3.addSequenceFeature(new SequenceFeature("disulfide bond", "desc", + 8, 12, 0f, null)); /* * select the first five columns --> Metal in seq1 cols 4-5 @@ -86,9 +91,18 @@ public class AlignViewControllerTest sg.addSequence(seq3, false); sg.addSequence(seq4, false); + /* + * set features visible on a viewport as only visible features are selected + */ + AlignFrame af = new AlignFrame(new Alignment(new SequenceI[] { seq1, + seq2, seq3, seq4 }), 100, 100); + af.getFeatureRenderer().findAllFeatures(true); + + AlignViewController avc = new AlignViewController(af, af.getViewport(), + af.alignPanel); + BitSet bs = new BitSet(); - int seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, - bs); + int seqCount = avc.findColumnsWithFeature("Metal", sg, bs); assertEquals(1, seqCount); assertEquals(2, bs.cardinality()); assertTrue(bs.get(3)); // base 0 @@ -99,7 +113,7 @@ public class AlignViewControllerTest */ sg.setEndRes(6); bs.clear(); - seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs); + seqCount = avc.findColumnsWithFeature("Metal", sg, bs); assertEquals(2, seqCount); assertEquals(4, bs.cardinality()); assertTrue(bs.get(3)); @@ -113,7 +127,7 @@ public class AlignViewControllerTest sg.setStartRes(13); sg.setEndRes(13); bs.clear(); - seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs); + seqCount = avc.findColumnsWithFeature("Metal", sg, bs); assertEquals(1, seqCount); assertEquals(1, bs.cardinality()); assertTrue(bs.get(13)); @@ -124,18 +138,35 @@ public class AlignViewControllerTest sg.setStartRes(17); sg.setEndRes(19); bs.clear(); - seqCount = AlignViewController.findColumnsWithFeature("Metal", sg, bs); + seqCount = avc.findColumnsWithFeature("Metal", sg, bs); assertEquals(0, seqCount); assertEquals(0, bs.cardinality()); /* + * threshold Metal to hide where score < 5 + * 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); + fc.setAboveThreshold(true); + fc.setThreshold(5f); + af.getFeatureRenderer().setColour("Metal", fc); + sg.setStartRes(0); + sg.setEndRes(6); + bs.clear(); + seqCount = avc.findColumnsWithFeature("Metal", sg, bs); + assertEquals(1, seqCount); + assertEquals(2, bs.cardinality()); + assertTrue(bs.get(5)); + assertTrue(bs.get(6)); + + /* * columns 11-13 should not match disulfide bond at 8/12 */ sg.setStartRes(10); sg.setEndRes(12); bs.clear(); - seqCount = AlignViewController.findColumnsWithFeature("disulfide bond", - sg, bs); + seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs); assertEquals(0, seqCount); assertEquals(0, bs.cardinality()); @@ -145,8 +176,7 @@ public class AlignViewControllerTest sg.setStartRes(5); sg.setEndRes(17); bs.clear(); - seqCount = AlignViewController.findColumnsWithFeature("disulfide bond", - sg, bs); + seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs); assertEquals(1, seqCount); assertEquals(2, bs.cardinality()); assertTrue(bs.get(8)); @@ -158,7 +188,7 @@ public class AlignViewControllerTest sg.setStartRes(0); sg.setEndRes(19); bs.clear(); - seqCount = AlignViewController.findColumnsWithFeature("Pfam", sg, bs); + seqCount = avc.findColumnsWithFeature("Pfam", sg, bs); assertEquals(0, seqCount); assertEquals(0, bs.cardinality()); } diff --git a/test/jalview/datamodel/AlignmentAnnotationTests.java b/test/jalview/datamodel/AlignmentAnnotationTests.java index e47e9d6..19a725e 100644 --- a/test/jalview/datamodel/AlignmentAnnotationTests.java +++ b/test/jalview/datamodel/AlignmentAnnotationTests.java @@ -20,8 +20,8 @@ */ package jalview.datamodel; +import static org.testng.Assert.assertNull; import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; import jalview.analysis.AlignSeq; import jalview.gui.JvOptionPane; @@ -335,4 +335,92 @@ public class AlignmentAnnotationTests Assert.assertTrue(ann.isQuantitative(), "Mixed 'E' annotation set should be quantitative."); } + + @Test(groups = "Functional") + public void testMakeVisibleAnnotation() + { + HiddenColumns h = new HiddenColumns(); + Annotation[] anns = new Annotation[] { null, null, new Annotation(1), + new Annotation(2), new Annotation(3), null, null, new Annotation(4), + new Annotation(5), new Annotation(6), new Annotation(7), + new Annotation(8) }; + AlignmentAnnotation ann = new AlignmentAnnotation("an", "some an", + anns); + + // null annotations + AlignmentAnnotation emptyann = new AlignmentAnnotation("an", "some ann", + null); + emptyann.makeVisibleAnnotation(h); + assertNull(emptyann.annotations); + + emptyann.makeVisibleAnnotation(3, 4, h); + assertNull(emptyann.annotations); + + // without bounds, does everything + ann.makeVisibleAnnotation(h); + assertEquals(12, ann.annotations.length); + assertNull(ann.annotations[0]); + assertNull(ann.annotations[1]); + assertEquals(1.0f, ann.annotations[2].value); + assertEquals(2.0f, ann.annotations[3].value); + assertEquals(3.0f, ann.annotations[4].value); + assertNull(ann.annotations[5]); + assertNull(ann.annotations[6]); + assertEquals(4.0f, ann.annotations[7].value); + assertEquals(5.0f, ann.annotations[8].value); + assertEquals(6.0f, ann.annotations[9].value); + assertEquals(7.0f, ann.annotations[10].value); + assertEquals(8.0f, ann.annotations[11].value); + + // without hidden cols, just truncates + ann.makeVisibleAnnotation(3, 5, h); + assertEquals(3, ann.annotations.length); + assertEquals(2.0f, ann.annotations[0].value); + assertEquals(3.0f, ann.annotations[1].value); + assertNull(ann.annotations[2]); + + anns = new Annotation[] { null, null, new Annotation(1), + new Annotation(2), new Annotation(3), null, null, new Annotation(4), + new Annotation(5), new Annotation(6), new Annotation(7), + new Annotation(8) }; + ann = new AlignmentAnnotation("an", "some an", anns); + h.hideColumns(4, 7); + ann.makeVisibleAnnotation(1, 9, h); + assertEquals(5, ann.annotations.length); + assertNull(ann.annotations[0]); + assertEquals(1.0f, ann.annotations[1].value); + assertEquals(2.0f, ann.annotations[2].value); + assertEquals(5.0f, ann.annotations[3].value); + assertEquals(6.0f, ann.annotations[4].value); + + anns = new Annotation[] { null, null, new Annotation(1), + new Annotation(2), new Annotation(3), null, null, new Annotation(4), + new Annotation(5), new Annotation(6), new Annotation(7), + new Annotation(8) }; + ann = new AlignmentAnnotation("an", "some an", anns); + h.hideColumns(1, 2); + ann.makeVisibleAnnotation(1, 9, h); + assertEquals(3, ann.annotations.length); + assertEquals(2.0f, ann.annotations[0].value); + assertEquals(5.0f, ann.annotations[1].value); + assertEquals(6.0f, ann.annotations[2].value); + + anns = new Annotation[] { null, null, new Annotation(1), + new Annotation(2), new Annotation(3), null, null, new Annotation(4), + new Annotation(5), new Annotation(6), new Annotation(7), + new Annotation(8), new Annotation(9), new Annotation(10), + new Annotation(11), new Annotation(12), new Annotation(13), + new Annotation(14), new Annotation(15) }; + ann = new AlignmentAnnotation("an", "some an", anns); + h = new HiddenColumns(); + h.hideColumns(5, 18); + h.hideColumns(20, 21); + ann.makeVisibleAnnotation(1, 21, h); + assertEquals(5, ann.annotations.length); + assertEquals(1.0f, ann.annotations[1].value); + assertEquals(2.0f, ann.annotations[2].value); + assertEquals(3.0f, ann.annotations[3].value); + assertNull(ann.annotations[0]); + assertNull(ann.annotations[4]); + } } diff --git a/test/jalview/datamodel/AlignmentTest.java b/test/jalview/datamodel/AlignmentTest.java index 4b5d096..1d1ebd6 100644 --- a/test/jalview/datamodel/AlignmentTest.java +++ b/test/jalview/datamodel/AlignmentTest.java @@ -34,6 +34,7 @@ import jalview.io.DataSourceType; import jalview.io.FileFormat; import jalview.io.FileFormatI; import jalview.io.FormatAdapter; +import jalview.util.Comparison; import jalview.util.MapList; import java.io.IOException; @@ -247,7 +248,9 @@ public class AlignmentTest if (raiseAssert) { Assert.fail(message - + " DBRefEntry for sequence in alignment had map to sequence not in dataset"); + + " DBRefEntry " + dbr + " for sequence " + + seqds + + " in alignment has map to sequence not in dataset"); } return false; } @@ -668,6 +671,17 @@ public class AlignmentTest // third found.. so assertFalse(iter.hasNext()); + // search for annotation on one sequence with a particular label - expect + // one + SequenceI sqfound; + anns = al.findAnnotations(sqfound = al.getSequenceAt(1), null, + "Secondary Structure"); + iter = anns.iterator(); + assertTrue(iter.hasNext()); + // expect reference to sequence 1 in the alignment + assertTrue(sqfound == iter.next().sequenceRef); + assertFalse(iter.hasNext()); + // null on all parameters == find all annotations anns = al.findAnnotations(null, null, null); iter = anns.iterator(); @@ -1321,4 +1335,153 @@ public class AlignmentTest // todo test coverage for annotations, mappings, groups, // hidden sequences, properties } + + /** + * test that calcId == null on findOrCreate doesn't raise an NPE, and yields + * an annotation with a null calcId + * + */ + @Test(groups = "Functional") + public void testFindOrCreateForNullCalcId() + { + SequenceI seq = new Sequence("seq1", "FRMLPSRT-A--L-"); + AlignmentI alignment = new Alignment(new SequenceI[] { seq }); + + AlignmentAnnotation ala = alignment.findOrCreateAnnotation( + "Temperature Factor", null, false, seq, null); + assertNotNull(ala); + assertEquals(seq, ala.sequenceRef); + assertEquals("", ala.calcId); + } + + @Test(groups = "Functional") + public void testPropagateInsertions() + { + // create an alignment with no gaps - this will be the profile seq and other + // JPRED seqs + AlignmentGenerator gen = new AlignmentGenerator(false); + AlignmentI al = gen.generate(25, 10, 1234, 0, 0); + + // get the profileseq + SequenceI profileseq = al.getSequenceAt(0); + SequenceI gappedseq = new Sequence(profileseq); + gappedseq.insertCharAt(5, al.getGapCharacter()); + gappedseq.insertCharAt(6, al.getGapCharacter()); + gappedseq.insertCharAt(7, al.getGapCharacter()); + gappedseq.insertCharAt(8, al.getGapCharacter()); + + // force different kinds of padding + al.getSequenceAt(3).deleteChars(2, 23); + al.getSequenceAt(4).deleteChars(2, 27); + al.getSequenceAt(5).deleteChars(10, 27); + + // create an alignment view with the gapped sequence + SequenceI[] seqs = new SequenceI[1]; + seqs[0] = gappedseq; + AlignmentI newal = new Alignment(seqs); + HiddenColumns hidden = new HiddenColumns(); + hidden.hideColumns(15, 17); + + AlignmentView view = new AlignmentView(newal, hidden, null, true, false, + false); + + // confirm that original contigs are as expected + Iterator visible = hidden.getVisContigsIterator(0, 25, false); + int[] region = visible.next(); + assertEquals("[0, 14]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[18, 24]", Arrays.toString(region)); + + // propagate insertions + HiddenColumns result = al.propagateInsertions(profileseq, view); + + // confirm that the contigs have changed to account for the gaps + visible = result.getVisContigsIterator(0, 25, false); + region = visible.next(); + assertEquals("[0, 10]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[14, 24]", Arrays.toString(region)); + + // confirm the alignment has been changed so that the other sequences have + // gaps inserted where the columns are hidden + assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[10])); + assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[11])); + assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[12])); + assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[13])); + assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[14])); + + } + + @Test(groups = "Functional") + public void testPropagateInsertionsOverlap() + { + // test propagateInsertions where gaps and hiddenColumns overlap + + // create an alignment with no gaps - this will be the profile seq and other + // JPRED seqs + AlignmentGenerator gen = new AlignmentGenerator(false); + AlignmentI al = gen.generate(20, 10, 1234, 0, 0); + + // get the profileseq + SequenceI profileseq = al.getSequenceAt(0); + SequenceI gappedseq = new Sequence(profileseq); + gappedseq.insertCharAt(5, al.getGapCharacter()); + gappedseq.insertCharAt(6, al.getGapCharacter()); + gappedseq.insertCharAt(7, al.getGapCharacter()); + gappedseq.insertCharAt(8, al.getGapCharacter()); + + // create an alignment view with the gapped sequence + SequenceI[] seqs = new SequenceI[1]; + seqs[0] = gappedseq; + AlignmentI newal = new Alignment(seqs); + + // hide columns so that some overlap with the gaps + HiddenColumns hidden = new HiddenColumns(); + hidden.hideColumns(7, 10); + + AlignmentView view = new AlignmentView(newal, hidden, null, true, false, + false); + + // confirm that original contigs are as expected + Iterator visible = hidden.getVisContigsIterator(0, 20, false); + int[] region = visible.next(); + assertEquals("[0, 6]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[11, 19]", Arrays.toString(region)); + assertFalse(visible.hasNext()); + + // propagate insertions + HiddenColumns result = al.propagateInsertions(profileseq, view); + + // confirm that the contigs have changed to account for the gaps + visible = result.getVisContigsIterator(0, 20, false); + region = visible.next(); + assertEquals("[0, 4]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[7, 19]", Arrays.toString(region)); + assertFalse(visible.hasNext()); + + // confirm the alignment has been changed so that the other sequences have + // gaps inserted where the columns are hidden + assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[4])); + assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[5])); + assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[6])); + assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[7])); + } + + @Test(groups = { "Functional" }) + public void testPadGaps() + { + SequenceI seq1 = new Sequence("seq1", "ABCDEF--"); + SequenceI seq2 = new Sequence("seq2", "-JKLMNO--"); + SequenceI seq3 = new Sequence("seq2", "-PQR"); + AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2, seq3 }); + a.setGapCharacter('.'); // this replaces existing gaps + assertEquals("ABCDEF..", seq1.getSequenceAsString()); + a.padGaps(); + // trailing gaps are pruned, short sequences padded with gap character + assertEquals("ABCDEF.", seq1.getSequenceAsString()); + assertEquals(".JKLMNO", seq2.getSequenceAsString()); + assertEquals(".PQR...", seq3.getSequenceAsString()); + } } diff --git a/test/jalview/datamodel/ColumnSelectionTest.java b/test/jalview/datamodel/ColumnSelectionTest.java index e99e952..8709961 100644 --- a/test/jalview/datamodel/ColumnSelectionTest.java +++ b/test/jalview/datamodel/ColumnSelectionTest.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.ConcurrentModificationException; +import java.util.Iterator; import java.util.List; import org.testng.annotations.BeforeClass; @@ -132,9 +133,9 @@ public class ColumnSelectionTest // hide column 5 (and adjacent): cs.hideSelectedColumns(5, al.getHiddenColumns()); // 4,5,6 now hidden: - List hidden = al.getHiddenColumns().getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); + Iterator regions = al.getHiddenColumns().iterator(); + assertEquals(1, al.getHiddenColumns().getNumberOfRegions()); + assertEquals("[4, 6]", Arrays.toString(regions.next())); // none now selected: assertTrue(cs.getSelected().isEmpty()); @@ -145,9 +146,9 @@ public class ColumnSelectionTest cs.addElement(5); cs.addElement(6); cs.hideSelectedColumns(4, al.getHiddenColumns()); - hidden = al.getHiddenColumns().getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); + regions = al.getHiddenColumns().iterator(); + assertEquals(1, al.getHiddenColumns().getNumberOfRegions()); + assertEquals("[4, 6]", Arrays.toString(regions.next())); assertTrue(cs.getSelected().isEmpty()); // repeat, hiding column (4, 5 and) 6 @@ -157,9 +158,9 @@ public class ColumnSelectionTest cs.addElement(5); cs.addElement(6); cs.hideSelectedColumns(6, al.getHiddenColumns()); - hidden = al.getHiddenColumns().getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); + regions = al.getHiddenColumns().iterator(); + assertEquals(1, al.getHiddenColumns().getNumberOfRegions()); + assertEquals("[4, 6]", Arrays.toString(regions.next())); assertTrue(cs.getSelected().isEmpty()); // repeat, with _only_ adjacent columns selected @@ -168,9 +169,9 @@ public class ColumnSelectionTest cs.addElement(4); cs.addElement(6); cs.hideSelectedColumns(5, al.getHiddenColumns()); - hidden = al.getHiddenColumns().getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); + regions = al.getHiddenColumns().iterator(); + assertEquals(1, al.getHiddenColumns().getNumberOfRegions()); + assertEquals("[4, 6]", Arrays.toString(regions.next())); assertTrue(cs.getSelected().isEmpty()); } @@ -196,12 +197,12 @@ public class ColumnSelectionTest cs.hideSelectedColumns(al); assertTrue(cs.getSelected().isEmpty()); - List hidden = cols.getHiddenColumnsCopy(); - assertEquals(4, hidden.size()); - assertEquals("[2, 4]", Arrays.toString(hidden.get(0))); - assertEquals("[7, 9]", Arrays.toString(hidden.get(1))); - assertEquals("[15, 18]", Arrays.toString(hidden.get(2))); - assertEquals("[20, 22]", Arrays.toString(hidden.get(3))); + Iterator regions = cols.iterator(); + assertEquals(4, cols.getNumberOfRegions()); + assertEquals("[2, 4]", Arrays.toString(regions.next())); + assertEquals("[7, 9]", Arrays.toString(regions.next())); + assertEquals("[15, 18]", Arrays.toString(regions.next())); + assertEquals("[20, 22]", Arrays.toString(regions.next())); } /** diff --git a/test/jalview/datamodel/HiddenColumnsCursorTest.java b/test/jalview/datamodel/HiddenColumnsCursorTest.java new file mode 100644 index 0000000..97402b8 --- /dev/null +++ b/test/jalview/datamodel/HiddenColumnsCursorTest.java @@ -0,0 +1,157 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import static org.testng.Assert.assertNull; +import static org.testng.AssertJUnit.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +public class HiddenColumnsCursorTest +{ + + @Test(groups = { "Functional" }) + public void testConstructor() + { + HiddenColumnsCursor cursor = new HiddenColumnsCursor(); + assertNull(cursor.findRegionForColumn(0, false)); + + List hlist = new ArrayList<>(); + cursor = new HiddenColumnsCursor(hlist); + assertNull(cursor.findRegionForColumn(0, false)); + + cursor = new HiddenColumnsCursor(hlist, 3, 12); + assertNull(cursor.findRegionForColumn(0, false)); + + hlist.add(new int[] { 3, 7 }); + hlist.add(new int[] { 15, 25 }); + cursor = new HiddenColumnsCursor(hlist); + HiddenCursorPosition p = cursor.findRegionForColumn(8, false); + assertEquals(1, p.getRegionIndex()); + + cursor = new HiddenColumnsCursor(hlist, 1, 5); + p = cursor.findRegionForColumn(8, false); + assertEquals(1, p.getRegionIndex()); + } + + /** + * Test the method which finds the corresponding region given a column + */ + @Test(groups = { "Functional" }) + public void testFindRegionForColumn() + { + HiddenColumnsCursor cursor = new HiddenColumnsCursor(); + + HiddenCursorPosition pos = cursor.findRegionForColumn(20, false); + assertNull(pos); + + List hidden = new ArrayList<>(); + hidden.add(new int[] { 53, 76 }); + hidden.add(new int[] { 104, 125 }); + + cursor = new HiddenColumnsCursor(hidden); + + int regionIndex = cursor.findRegionForColumn(126, false).getRegionIndex(); + assertEquals(2, regionIndex); + + regionIndex = cursor.findRegionForColumn(125, false).getRegionIndex(); + assertEquals(1, regionIndex); + + regionIndex = cursor.findRegionForColumn(108, false).getRegionIndex(); + assertEquals(1, regionIndex); + + regionIndex = cursor.findRegionForColumn(104, false).getRegionIndex(); + assertEquals(1, regionIndex); + + regionIndex = cursor.findRegionForColumn(103, false).getRegionIndex(); + assertEquals(1, regionIndex); + + regionIndex = cursor.findRegionForColumn(77, false).getRegionIndex(); + assertEquals(1, regionIndex); + + regionIndex = cursor.findRegionForColumn(76, false).getRegionIndex(); + assertEquals(0, regionIndex); + + regionIndex = cursor.findRegionForColumn(53, false).getRegionIndex(); + assertEquals(0, regionIndex); + + regionIndex = cursor.findRegionForColumn(52, false).getRegionIndex(); + assertEquals(0, regionIndex); + + regionIndex = cursor.findRegionForColumn(0, false).getRegionIndex(); + assertEquals(0, regionIndex); + + hidden.add(new int[] { 138, 155 }); + + cursor = new HiddenColumnsCursor(hidden); + + regionIndex = cursor.findRegionForColumn(160, false).getRegionIndex(); + assertEquals(3, regionIndex); + + regionIndex = cursor.findRegionForColumn(100, false).getRegionIndex(); + assertEquals(1, regionIndex); + } + + /** + * Test the method which counts the number of hidden columns before a column + */ + @Test(groups = { "Functional" }) + public void testFindRegionForColumn_Visible() + { + HiddenColumnsCursor cursor = new HiddenColumnsCursor(); + + HiddenCursorPosition pos = cursor.findRegionForColumn(20, true); + assertNull(pos); + + List hidden = new ArrayList<>(); + hidden.add(new int[] { 53, 76 }); + hidden.add(new int[] { 104, 125 }); + + cursor = new HiddenColumnsCursor(hidden); + + int offset = cursor.findRegionForColumn(80, true).getHiddenSoFar(); + assertEquals(46, offset); + + offset = cursor.findRegionForColumn(79, true).getHiddenSoFar(); + assertEquals(24, offset); + + offset = cursor.findRegionForColumn(53, true).getHiddenSoFar(); + assertEquals(24, offset); + + offset = cursor.findRegionForColumn(52, true).getHiddenSoFar(); + assertEquals(0, offset); + + offset = cursor.findRegionForColumn(10, true).getHiddenSoFar(); + assertEquals(0, offset); + + offset = cursor.findRegionForColumn(0, true).getHiddenSoFar(); + assertEquals(0, offset); + + offset = cursor.findRegionForColumn(79, true).getHiddenSoFar(); + assertEquals(24, offset); + + offset = cursor.findRegionForColumn(80, true).getHiddenSoFar(); + assertEquals(46, offset); + } +} diff --git a/test/jalview/datamodel/HiddenColumnsTest.java b/test/jalview/datamodel/HiddenColumnsTest.java index 7c88d71..2916199 100644 --- a/test/jalview/datamodel/HiddenColumnsTest.java +++ b/test/jalview/datamodel/HiddenColumnsTest.java @@ -26,26 +26,15 @@ import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import jalview.analysis.AlignmentGenerator; -import jalview.gui.JvOptionPane; import java.util.Arrays; import java.util.BitSet; -import java.util.List; -import java.util.Random; +import java.util.Iterator; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class HiddenColumnsTest { - - @BeforeClass(alwaysRun = true) - public void setUpJvOptionPane() - { - JvOptionPane.setInteractiveMode(false); - JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); - } - /** * Test the method which counts the number of hidden columns */ @@ -77,22 +66,22 @@ public class HiddenColumnsTest public void testFindColumnPosition() { HiddenColumns cs = new HiddenColumns(); - assertEquals(5, cs.findColumnPosition(5)); + assertEquals(5, cs.absoluteToVisibleColumn(5)); // hiding column 6 makes no difference cs.hideColumns(6, 6); - assertEquals(5, cs.findColumnPosition(5)); + assertEquals(5, cs.absoluteToVisibleColumn(5)); // hiding column 4 moves column 5 to column 4 cs.hideColumns(4, 4); - assertEquals(4, cs.findColumnPosition(5)); + assertEquals(4, cs.absoluteToVisibleColumn(5)); // hiding column 4 moves column 4 to position 3 - assertEquals(3, cs.findColumnPosition(4)); + assertEquals(3, cs.absoluteToVisibleColumn(4)); // hiding columns 1 and 2 moves column 5 to column 2 cs.hideColumns(1, 2); - assertEquals(2, cs.findColumnPosition(5)); + assertEquals(2, cs.absoluteToVisibleColumn(5)); // check with > 1 hidden column regions // where some columns are in the hidden regions @@ -102,105 +91,82 @@ public class HiddenColumnsTest cs2.hideColumns(40, 44); // hiding columns 5-10 and 20-27 moves column 8 to column 4 - assertEquals(4, cs2.findColumnPosition(8)); + assertEquals(4, cs2.absoluteToVisibleColumn(8)); // and moves column 24 to 13 - assertEquals(13, cs2.findColumnPosition(24)); + assertEquals(13, cs2.absoluteToVisibleColumn(24)); // and moves column 28 to 14 - assertEquals(14, cs2.findColumnPosition(28)); + assertEquals(14, cs2.absoluteToVisibleColumn(28)); // and moves column 40 to 25 - assertEquals(25, cs2.findColumnPosition(40)); + assertEquals(25, cs2.absoluteToVisibleColumn(40)); // check when hidden columns start at 0 that the visible column // is returned as 0 HiddenColumns cs3 = new HiddenColumns(); cs3.hideColumns(0, 4); - assertEquals(0, cs3.findColumnPosition(2)); + assertEquals(0, cs3.absoluteToVisibleColumn(2)); + // check that column after the last hidden region doesn't crash + assertEquals(46, cs2.absoluteToVisibleColumn(65)); } - /** - * Test the method that finds the visible column position a given distance - * before another column - */ @Test(groups = { "Functional" }) - public void testFindColumnNToLeft() + public void testVisibleContigsIterator() { HiddenColumns cs = new HiddenColumns(); - // test that without hidden columns, findColumnNToLeft returns - // position n to left of provided position - int pos = cs.subtractVisibleColumns(3, 10); - assertEquals(7, pos); - - // 0 returns same position - pos = cs.subtractVisibleColumns(0, 10); - assertEquals(10, pos); - - // overflow to left returns negative number - pos = cs.subtractVisibleColumns(3, 0); - assertEquals(-3, pos); - - // test that with hidden columns to left of result column - // behaviour is the same as above - cs.hideColumns(1, 3); - - // position n to left of provided position - pos = cs.subtractVisibleColumns(3, 10); - assertEquals(7, pos); - - // 0 returns same position - pos = cs.subtractVisibleColumns(0, 10); - assertEquals(10, pos); - - // test with one set of hidden columns between start and required position - cs.hideColumns(12, 15); - pos = cs.subtractVisibleColumns(8, 17); - assertEquals(5, pos); - - // test with two sets of hidden columns between start and required position - cs.hideColumns(20, 21); - pos = cs.subtractVisibleColumns(8, 23); - assertEquals(9, pos); - - // repeat last 2 tests with no hidden columns to left of required position - ColumnSelection colsel = new ColumnSelection(); - cs.revealAllHiddenColumns(colsel); - - // test with one set of hidden columns between start and required position - cs.hideColumns(12, 15); - pos = cs.subtractVisibleColumns(8, 17); - assertEquals(5, pos); - - // test with two sets of hidden columns between start and required position - cs.hideColumns(20, 21); - pos = cs.subtractVisibleColumns(8, 23); - assertEquals(9, pos); - - } + Iterator visible = cs.getVisContigsIterator(3, 10, false); + int[] region = visible.next(); + assertEquals("[3, 9]", Arrays.toString(region)); + assertFalse(visible.hasNext()); - @Test(groups = { "Functional" }) - public void testGetVisibleContigs() - { - HiddenColumns cs = new HiddenColumns(); cs.hideColumns(3, 6); cs.hideColumns(8, 9); cs.hideColumns(12, 12); - // start position is inclusive, end position exclusive: - int[] visible = cs.getVisibleContigs(1, 13); - assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible)); - - visible = cs.getVisibleContigs(4, 14); - assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible)); - - visible = cs.getVisibleContigs(3, 10); - assertEquals("[7, 7]", Arrays.toString(visible)); - - visible = cs.getVisibleContigs(4, 6); - assertEquals("[]", Arrays.toString(visible)); + // Test both ends visible region + + // start position is inclusive, end position exclusive + visible = cs.getVisContigsIterator(1, 13, false); + region = visible.next(); + assertEquals("[1, 2]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[7, 7]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[10, 11]", Arrays.toString(region)); + assertFalse(visible.hasNext()); + + // Test start hidden, end visible + visible = cs.getVisContigsIterator(4, 14, false); + region = visible.next(); + assertEquals("[7, 7]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[10, 11]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[13, 13]", Arrays.toString(region)); + assertFalse(visible.hasNext()); + + // Test start hidden, end hidden + visible = cs.getVisContigsIterator(3, 10, false); + region = visible.next(); + assertEquals("[7, 7]", Arrays.toString(region)); + assertFalse(visible.hasNext()); + + // Test start visible, end hidden + visible = cs.getVisContigsIterator(0, 13, false); + region = visible.next(); + assertEquals("[0, 2]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[7, 7]", Arrays.toString(region)); + region = visible.next(); + assertEquals("[10, 11]", Arrays.toString(region)); + assertFalse(visible.hasNext()); + + // Test empty result + visible = cs.getVisContigsIterator(4, 6, false); + assertFalse(visible.hasNext()); } @Test(groups = { "Functional" }) @@ -216,14 +182,31 @@ public class HiddenColumnsTest assertFalse(cs.equals(cs2)); assertFalse(cs2.equals(cs)); + // with the wrong kind of object + assertFalse(cs.equals(new HiddenColumnsCursor())); + + // with a different hiddenColumns object - by size + HiddenColumns cs3 = new HiddenColumns(); + cs3.hideColumns(2, 3); + assertFalse(cs.equals(cs3)); + // with hidden columns added in a different order cs2.hideColumns(6, 9); + assertFalse(cs.equals(cs2)); + assertFalse(cs2.equals(cs)); + cs2.hideColumns(5, 8); assertTrue(cs.equals(cs2)); assertTrue(cs.equals(cs)); assertTrue(cs2.equals(cs)); assertTrue(cs2.equals(cs2)); + + // different ranges, same size + cs.hideColumns(10, 12); + cs2.hideColumns(10, 15); + assertFalse(cs.equals(cs2)); + } @Test(groups = "Functional") @@ -232,102 +215,50 @@ public class HiddenColumnsTest HiddenColumns cs = new HiddenColumns(); cs.hideColumns(10, 11); cs.hideColumns(5, 7); + Iterator regions = cs.iterator(); assertEquals("[5, 7]", - Arrays.toString(cs.getHiddenColumnsCopy().get(0))); + Arrays.toString(regions.next())); HiddenColumns cs2 = new HiddenColumns(cs); + regions = cs2.iterator(); assertTrue(cs2.hasHiddenColumns()); - assertEquals(2, cs2.getHiddenColumnsCopy().size()); + assertEquals(2, cs2.getNumberOfRegions()); // hidden columns are held in column order assertEquals("[5, 7]", - Arrays.toString(cs2.getHiddenColumnsCopy().get(0))); + Arrays.toString(regions.next())); assertEquals("[10, 11]", - Arrays.toString(cs2.getHiddenColumnsCopy().get(1))); + Arrays.toString(regions.next())); } - /** - * Test the code used to locate the reference sequence ruler origin - */ - @Test(groups = { "Functional" }) - public void testLocateVisibleBoundsofSequence() + @Test(groups = "Functional") + public void testCopyConstructor2() { - // create random alignment - AlignmentGenerator gen = new AlignmentGenerator(false); - AlignmentI al = gen.generate(50, 20, 123, 5, 5); + HiddenColumns cs = new HiddenColumns(); + cs.hideColumns(10, 11); + cs.hideColumns(5, 7); - HiddenColumns cs = al.getHiddenColumns(); - ColumnSelection colsel = new ColumnSelection(); + HiddenColumns cs2 = new HiddenColumns(cs, 3, 9, 1); + assertTrue(cs2.hasHiddenColumns()); + Iterator regions = cs2.iterator(); - SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---"); - assertEquals(2, seq.findIndex(seq.getStart())); - - // no hidden columns - assertEquals( - Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1, - seq.findIndex(seq.getEnd()) - 1, seq.getStart(), - seq.getEnd(), seq.findIndex(seq.getStart()) - 1, - seq.findIndex(seq.getEnd()) - 1 }), - Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); - - // hidden column on gap after end of sequence - should not affect bounds - colsel.hideSelectedColumns(13, al.getHiddenColumns()); - assertEquals( - Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1, - seq.findIndex(seq.getEnd()) - 1, seq.getStart(), - seq.getEnd(), seq.findIndex(seq.getStart()) - 1, - seq.findIndex(seq.getEnd()) - 1 }), - Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); + // only [5,7] returned, offset by 1 + assertEquals("[4, 6]", + Arrays.toString(regions.next())); + assertEquals(3, cs2.getSize()); - cs.revealAllHiddenColumns(colsel); - // hidden column on gap before beginning of sequence - should vis bounds by - // one - colsel.hideSelectedColumns(0, al.getHiddenColumns()); - assertEquals( - Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2, - seq.findIndex(seq.getEnd()) - 2, seq.getStart(), - seq.getEnd(), seq.findIndex(seq.getStart()) - 1, - seq.findIndex(seq.getEnd()) - 1 }), - Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); - - cs.revealAllHiddenColumns(colsel); - // hide columns around most of sequence - leave one residue remaining - cs.hideColumns(1, 3); - cs.hideColumns(6, 11); - assertEquals("-D", - cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]); - assertEquals( - Arrays.toString(new int[] { 1, 1, 3, 3, - seq.findIndex(seq.getStart()) - 1, - seq.findIndex(seq.getEnd()) - 1 }), - Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); - cs.revealAllHiddenColumns(colsel); + cs2 = new HiddenColumns(cs, 8, 15, 4); + regions = cs2.iterator(); + assertTrue(cs2.hasHiddenColumns()); - // hide whole sequence - should just get location of hidden region - // containing sequence - cs.hideColumns(1, 11); - assertEquals( - Arrays.toString(new int[] { 0, 1, 0, 0, - seq.findIndex(seq.getStart()) - 1, - seq.findIndex(seq.getEnd()) - 1 }), - Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); + // only [10,11] returned, offset by 4 + assertEquals("[6, 7]", + Arrays.toString(regions.next())); + assertEquals(2, cs2.getSize()); + cs2 = new HiddenColumns(cs, 6, 10, 4); + assertFalse(cs2.hasHiddenColumns()); } - @Test(groups = { "Functional" }) - public void testLocateVisibleBoundsPathologicals() - { - // test some pathological cases we missed - AlignmentI al = new Alignment(new SequenceI[] { new Sequence( - "refseqGaptest", "KTDVTI----------NFI-----G----L") }); - HiddenColumns cs = new HiddenColumns(); - cs.hideInsertionsFor(al.getSequenceAt(0)); - assertEquals( - "G", - "" - + al.getSequenceAt(0).getCharAt( - cs.adjustForHiddenColumns(9))); - - } @Test(groups = { "Functional" }) public void testHideColumns() @@ -339,80 +270,103 @@ public class HiddenColumnsTest ColumnSelection colsel = new ColumnSelection(); HiddenColumns cs = al.getHiddenColumns(); colsel.hideSelectedColumns(5, al.getHiddenColumns()); - List hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[5, 5]", Arrays.toString(hidden.get(0))); + Iterator regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[5, 5]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 1); colsel.hideSelectedColumns(3, al.getHiddenColumns()); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(2, hidden.size()); + regions = cs.iterator(); + assertEquals(2, cs.getNumberOfRegions()); // two hidden ranges, in order: - assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size()); - assertEquals("[3, 3]", Arrays.toString(hidden.get(0))); - assertEquals("[5, 5]", Arrays.toString(hidden.get(1))); + assertEquals("[3, 3]", Arrays.toString(regions.next())); + assertEquals("[5, 5]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 2); // hiding column 4 expands [3, 3] to [3, 4] // and merges to [5, 5] to make [3, 5] colsel.hideSelectedColumns(4, al.getHiddenColumns()); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[3, 5]", Arrays.toString(hidden.get(0))); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[3, 5]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 3); // clear hidden columns (note they are added to selected) cs.revealAllHiddenColumns(colsel); // it is now actually null but getter returns an empty list - assertTrue(cs.getHiddenColumnsCopy().isEmpty()); + assertEquals(0, cs.getNumberOfRegions()); + assertEquals(cs.getSize(), 0); cs.hideColumns(3, 6); - hidden = cs.getHiddenColumnsCopy(); - int[] firstHiddenRange = hidden.get(0); + regions = cs.iterator(); + int[] firstHiddenRange = regions.next(); assertEquals("[3, 6]", Arrays.toString(firstHiddenRange)); + assertEquals(cs.getSize(), 4); // adding a subrange of already hidden should do nothing cs.hideColumns(4, 5); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); assertEquals("[3, 6]", - Arrays.toString(cs.getHiddenColumnsCopy().get(0))); + Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 4); cs.hideColumns(3, 5); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); assertEquals("[3, 6]", - Arrays.toString(cs.getHiddenColumnsCopy().get(0))); + Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 4); cs.hideColumns(4, 6); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); assertEquals("[3, 6]", - Arrays.toString(cs.getHiddenColumnsCopy().get(0))); + Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 4); cs.hideColumns(3, 6); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); assertEquals("[3, 6]", - Arrays.toString(cs.getHiddenColumnsCopy().get(0))); + Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 4); cs.revealAllHiddenColumns(colsel); cs.hideColumns(2, 4); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[2, 4]", Arrays.toString(hidden.get(0))); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[2, 4]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 3); // extend contiguous with 2 positions overlap cs.hideColumns(3, 5); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[2, 5]", Arrays.toString(hidden.get(0))); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[2, 5]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 4); // extend contiguous with 1 position overlap cs.hideColumns(5, 6); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[2, 6]", Arrays.toString(hidden.get(0))); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[2, 6]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 5); // extend contiguous with overlap both ends: cs.hideColumns(1, 7); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[1, 7]", Arrays.toString(hidden.get(0))); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[1, 7]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 7); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(15, 18); + cs.hideColumns(2, 4); + cs.hideColumns(7, 9); + regions = cs.iterator(); + assertEquals(3, cs.getNumberOfRegions()); + assertEquals("[2, 4]", Arrays.toString(regions.next())); + assertEquals("[7, 9]", Arrays.toString(regions.next())); + assertEquals("[15, 18]", Arrays.toString(regions.next())); + assertEquals(cs.getSize(), 10); } /** @@ -424,11 +378,18 @@ public class HiddenColumnsTest { ColumnSelection colsel = new ColumnSelection(); HiddenColumns cs = new HiddenColumns(); + + // test with null hidden columns + cs.revealHiddenColumns(5, colsel); + assertTrue(colsel.getSelected().isEmpty()); + cs.hideColumns(5, 8); colsel.addElement(10); cs.revealHiddenColumns(5, colsel); - // hidden columns list now null but getter returns empty list: - assertTrue(cs.getHiddenColumnsCopy().isEmpty()); + + // hiddenColumns now empty + assertEquals(0, cs.getSize()); + // revealed columns are marked as selected (added to selection): assertEquals("[10, 5, 6, 7, 8]", colsel.getSelected().toString()); @@ -436,36 +397,67 @@ public class HiddenColumnsTest colsel = new ColumnSelection(); cs = new HiddenColumns(); cs.hideColumns(5, 8); - List hidden = cs.getHiddenColumnsCopy(); + + int prevSize = cs.getSize(); cs.revealHiddenColumns(6, colsel); - assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size()); + assertEquals(prevSize, cs.getSize()); + assertTrue(colsel.getSelected().isEmpty()); + + // reveal hidden columns when there is more than one region + cs.hideColumns(20, 23); + // now there are 2 hidden regions + assertEquals(2, cs.getNumberOfRegions()); + + cs.revealHiddenColumns(20, colsel); + + // hiddenColumns now has one region + assertEquals(1, cs.getNumberOfRegions()); + + // revealed columns are marked as selected (added to selection): + assertEquals("[20, 21, 22, 23]", colsel.getSelected().toString()); + + // call with a column past the end of the hidden column ranges + colsel.clear(); + cs.revealHiddenColumns(20, colsel); + // hiddenColumns still has 1 region + assertEquals(1, cs.getNumberOfRegions()); assertTrue(colsel.getSelected().isEmpty()); } @Test(groups = { "Functional" }) public void testRevealAllHiddenColumns() { - HiddenColumns cs = new HiddenColumns(); + HiddenColumns hidden = new HiddenColumns(); ColumnSelection colsel = new ColumnSelection(); - cs.hideColumns(5, 8); - cs.hideColumns(2, 3); + + // test with null hidden columns + hidden.revealAllHiddenColumns(colsel); + assertTrue(colsel.getSelected().isEmpty()); + + hidden.hideColumns(5, 8); + hidden.hideColumns(2, 3); colsel.addElement(11); colsel.addElement(1); - cs.revealAllHiddenColumns(colsel); + hidden.revealAllHiddenColumns(colsel); /* * revealing hidden columns adds them (in order) to the (unordered) * selection list */ - assertTrue(cs.getHiddenColumnsCopy().isEmpty()); - assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", colsel.getSelected() - .toString()); + + // hiddenColumns now empty + assertEquals(0, hidden.getSize()); + + assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", + colsel.getSelected().toString()); } @Test(groups = { "Functional" }) public void testIsVisible() { HiddenColumns cs = new HiddenColumns(); + assertTrue(cs.isVisible(5)); + cs.hideColumns(2, 4); cs.hideColumns(6, 7); assertTrue(cs.isVisible(0)); @@ -477,6 +469,7 @@ public class HiddenColumnsTest assertTrue(cs.isVisible(5)); assertFalse(cs.isVisible(6)); assertFalse(cs.isVisible(7)); + assertTrue(cs.isVisible(8)); } /** @@ -493,15 +486,17 @@ public class HiddenColumnsTest HiddenColumns cs = new HiddenColumns(); cs.hideColumns(49, 59); cs.hideColumns(69, 79); - List hidden = cs.getHiddenColumnsCopy(); - assertEquals(2, hidden.size()); - assertEquals("[49, 59]", Arrays.toString(hidden.get(0))); - assertEquals("[69, 79]", Arrays.toString(hidden.get(1))); + Iterator regions = cs.iterator(); + assertEquals(2, cs.getNumberOfRegions()); + assertEquals("[49, 59]", Arrays.toString(regions.next())); + assertEquals("[69, 79]", Arrays.toString(regions.next())); + assertEquals(22, cs.getSize()); cs.hideColumns(48, 80); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[48, 80]", Arrays.toString(hidden.get(0))); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[48, 80]", Arrays.toString(regions.next())); + assertEquals(33, cs.getSize()); /* * another...joining hidden ranges @@ -512,9 +507,10 @@ public class HiddenColumnsTest cs.hideColumns(50, 60); // hiding 21-49 should merge to one range cs.hideColumns(21, 49); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(1, hidden.size()); - assertEquals("[10, 60]", Arrays.toString(hidden.get(0))); + regions = cs.iterator(); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals("[10, 60]", Arrays.toString(regions.next())); + assertEquals(51, cs.getSize()); /* * another...left overlap, subsumption, right overlap, @@ -528,14 +524,15 @@ public class HiddenColumnsTest cs.hideColumns(60, 70); cs.hideColumns(15, 45); - hidden = cs.getHiddenColumnsCopy(); - assertEquals(2, hidden.size()); - assertEquals("[10, 50]", Arrays.toString(hidden.get(0))); - assertEquals("[60, 70]", Arrays.toString(hidden.get(1))); + regions = cs.iterator(); + assertEquals(2, cs.getNumberOfRegions()); + assertEquals("[10, 50]", Arrays.toString(regions.next())); + assertEquals("[60, 70]", Arrays.toString(regions.next())); + assertEquals(52, cs.getSize()); } @Test(groups = { "Functional" }) - public void testHideBitset() + public void testHideColumns_BitSet() { HiddenColumns cs; @@ -544,80 +541,45 @@ public class HiddenColumnsTest // one hidden range one.set(1); cs = new HiddenColumns(); - cs.hideMarkedBits(one); - assertEquals(1, cs.getHiddenColumnsCopy().size()); + cs.hideColumns(one); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals(1, cs.getSize()); one.set(2); cs = new HiddenColumns(); - cs.hideMarkedBits(one); - assertEquals(1, cs.getHiddenColumnsCopy().size()); + cs.hideColumns(one); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals(2, cs.getSize()); one.set(3); cs = new HiddenColumns(); - cs.hideMarkedBits(one); - assertEquals(1, cs.getHiddenColumnsCopy().size()); + cs.hideColumns(one); + assertEquals(1, cs.getNumberOfRegions()); + assertEquals(3, cs.getSize()); // split one.clear(2); cs = new HiddenColumns(); - cs.hideMarkedBits(one); - assertEquals(2, cs.getHiddenColumnsCopy().size()); + cs.hideColumns(one); + assertEquals(2, cs.getNumberOfRegions()); + assertEquals(2, cs.getSize()); - assertEquals(0, cs.adjustForHiddenColumns(0)); - assertEquals(2, cs.adjustForHiddenColumns(1)); - assertEquals(4, cs.adjustForHiddenColumns(2)); + assertEquals(0, cs.visibleToAbsoluteColumn(0)); + assertEquals(2, cs.visibleToAbsoluteColumn(1)); + assertEquals(4, cs.visibleToAbsoluteColumn(2)); // one again one.clear(1); cs = new HiddenColumns(); - cs.hideMarkedBits(one); + cs.hideColumns(one); + assertEquals(1, cs.getSize()); - assertEquals(1, cs.getHiddenColumnsCopy().size()); + assertEquals(1, cs.getNumberOfRegions()); - assertEquals(0, cs.adjustForHiddenColumns(0)); - assertEquals(1, cs.adjustForHiddenColumns(1)); - assertEquals(2, cs.adjustForHiddenColumns(2)); - assertEquals(4, cs.adjustForHiddenColumns(3)); - } - - @Test(groups = { "Functional" }) - public void testGetBitset() - { - BitSet toMark, fromMark; - long seed = -3241532; - Random number = new Random(seed); - for (int n = 0; n < 1000; n++) - { - // create a random bitfield - toMark = BitSet.valueOf(new long[] { number.nextLong(), - number.nextLong(), number.nextLong() }); - toMark.set(n * number.nextInt(10), n * (25 + number.nextInt(25))); - HiddenColumns hc = new HiddenColumns(); - hc.hideMarkedBits(toMark); - - // see if we can recover bitfield - hc.markHiddenRegions(fromMark = new BitSet()); - assertEquals(toMark, fromMark); - } - } - - @Test(groups = { "Functional" }) - public void testFindHiddenRegionPositions() - { - HiddenColumns hc = new HiddenColumns(); - - List positions = hc.findHiddenRegionPositions(); - assertTrue(positions.isEmpty()); - - hc.hideColumns(3, 7); - hc.hideColumns(10, 10); - hc.hideColumns(14, 15); - - positions = hc.findHiddenRegionPositions(); - assertEquals(3, positions.size()); - assertEquals(3, positions.get(0).intValue()); - assertEquals(5, positions.get(1).intValue()); - assertEquals(8, positions.get(2).intValue()); + assertEquals(0, cs.visibleToAbsoluteColumn(0)); + assertEquals(1, cs.visibleToAbsoluteColumn(1)); + assertEquals(2, cs.visibleToAbsoluteColumn(2)); + assertEquals(4, cs.visibleToAbsoluteColumn(3)); } @Test(groups = { "Functional" }) @@ -637,7 +599,7 @@ public class HiddenColumnsTest } @Test(groups = "Functional") - public void getVisibleStartAndEndIndexTest() + public void testGetVisibleStartAndEndIndex() { Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); AlignmentI align = new Alignment(new SequenceI[] { seq }); @@ -663,6 +625,13 @@ public class HiddenColumnsTest System.out.println(startEnd[0] + " : " + startEnd[1]); assertEquals(1, startEnd[0]); assertEquals(23, startEnd[1]); + + // force lowest range to start of alignment + hc = new HiddenColumns(); + hc.hideColumns(3, 4); + startEnd = hc.getVisibleStartAndEndIndex(align.getWidth()); + assertEquals(0, startEnd[0]); + assertEquals(25, startEnd[1]); } @Test(groups = "Functional") @@ -677,58 +646,683 @@ public class HiddenColumnsTest hc.hideColumns(10, 10); hc.hideColumns(14, 15); - result = hc.getRegionWithEdgeAtRes(3); + result = hc.getRegionWithEdgeAtRes(2); assertEquals(3, result[0]); assertEquals(7, result[1]); + result = hc.getRegionWithEdgeAtRes(4); + assertEquals(10, result[0]); + assertEquals(10, result[1]); + result = hc.getRegionWithEdgeAtRes(5); assertEquals(10, result[0]); assertEquals(10, result[1]); result = hc.getRegionWithEdgeAtRes(6); assertNull(result); + + result = hc.getRegionWithEdgeAtRes(0); + assertNull(result); + + result = hc.getRegionWithEdgeAtRes(7); + assertEquals(14, result[0]); + assertEquals(15, result[1]); + + result = hc.getRegionWithEdgeAtRes(8); + assertEquals(14, result[0]); + assertEquals(15, result[1]); + + result = hc.getRegionWithEdgeAtRes(16); + assertNull(result); } @Test(groups = "Functional") - public void testPropagateInsertions() + public void testHasHiddenColumns() { - // create an alignment with no gaps - this will be the profile seq and other - // JPRED seqs - AlignmentGenerator gen = new AlignmentGenerator(false); - AlignmentI al = gen.generate(20, 10, 1234, 0, 0); - - // get the profileseq - SequenceI profileseq = al.getSequenceAt(0); - SequenceI gappedseq = new Sequence(profileseq); - gappedseq.insertCharAt(5, al.getGapCharacter()); - gappedseq.insertCharAt(6, al.getGapCharacter()); - gappedseq.insertCharAt(7, al.getGapCharacter()); - gappedseq.insertCharAt(8, al.getGapCharacter()); - - // create an alignment view with the gapped sequence - SequenceI[] seqs = new SequenceI[1]; - seqs[0] = gappedseq; - AlignmentI newal = new Alignment(seqs); - HiddenColumns hidden = new HiddenColumns(); - hidden.hideColumns(15, 17); - - AlignmentView view = new AlignmentView(newal, hidden, null, true, false, - false); - - // confirm that original contigs are as expected - int[] oldcontigs = hidden.getVisibleContigs(0, 20); - int[] testcontigs = { 0, 14, 18, 19 }; - assertTrue(Arrays.equals(oldcontigs, testcontigs)); - - // propagate insertions - HiddenColumns result = HiddenColumns.propagateInsertions(profileseq, al, - view); - - // confirm that the contigs have changed to account for the gaps - int[] newcontigs = result.getVisibleContigs(0, 20); - testcontigs[1] = 10; - testcontigs[2] = 14; - assertTrue(Arrays.equals(newcontigs, testcontigs)); - + HiddenColumns h = new HiddenColumns(); + + // new HiddenColumns2 has no hidden cols + assertFalse(h.hasHiddenColumns()); + + // some columns hidden, returns true + h.hideColumns(5, 10); + assertTrue(h.hasHiddenColumns()); + + // reveal columns, no hidden cols again + ColumnSelection sel = new ColumnSelection(); + h.revealAllHiddenColumns(sel); + assertFalse(h.hasHiddenColumns()); + } + + @Test(groups = "Functional") + public void testHasManyHiddenColumns() + { + HiddenColumns h = new HiddenColumns(); + + // h has no hidden cols + assertFalse(h.hasMultiHiddenColumnRegions()); + + // one set of columns hidden, returns false + h.hideColumns(5, 10); + assertFalse(h.hasMultiHiddenColumnRegions()); + + // two sets hidden, returns true + h.hideColumns(15, 17); + assertTrue(h.hasMultiHiddenColumnRegions()); + + // back to one block, asserts false + h.hideColumns(11, 14); + assertFalse(h.hasMultiHiddenColumnRegions()); + } + + @Test(groups = "Functional") + public void testAdjustForHiddenColumns() + { + HiddenColumns h = new HiddenColumns(); + // returns input value when there are no hidden columns + assertEquals(10, h.visibleToAbsoluteColumn(10)); + + h.hideColumns(20, 30); + assertEquals(10, h.visibleToAbsoluteColumn(10)); + assertEquals(20 + 11, h.visibleToAbsoluteColumn(20)); + assertEquals(35 + 11, h.visibleToAbsoluteColumn(35)); + + h.hideColumns(5, 7); + assertEquals(10 + 3, h.visibleToAbsoluteColumn(10)); + assertEquals(20 + 14, h.visibleToAbsoluteColumn(20)); + assertEquals(35 + 14, h.visibleToAbsoluteColumn(35)); + + ColumnSelection sel = new ColumnSelection(); + h.revealAllHiddenColumns(sel); + h.hideColumns(0, 1); + assertEquals(4, h.visibleToAbsoluteColumn(2)); + } + + @Test(groups = "Functional") + public void testGetNextHiddenBoundary_Left() + { + HiddenColumns h = new HiddenColumns(); + + // returns same value if no hidden cols + assertEquals(3, h.getNextHiddenBoundary(true, 3)); + + h.hideColumns(5, 10); + assertEquals(10, h.getNextHiddenBoundary(true, 15)); + assertEquals(3, h.getNextHiddenBoundary(true, 3)); + assertEquals(7, h.getNextHiddenBoundary(true, 7)); + + h.hideColumns(15, 20); + assertEquals(10, h.getNextHiddenBoundary(true, 15)); + assertEquals(20, h.getNextHiddenBoundary(true, 21)); + } + + @Test(groups = "Functional") + public void testGetNextHiddenBoundary_Right() + { + HiddenColumns h = new HiddenColumns(); + + // returns same value if no hidden cols + assertEquals(3, h.getNextHiddenBoundary(false, 3)); + + h.hideColumns(5, 10); + assertEquals(5, h.getNextHiddenBoundary(false, 3)); + assertEquals(15, h.getNextHiddenBoundary(false, 15)); + assertEquals(7, h.getNextHiddenBoundary(false, 7)); + + h.hideColumns(15, 20); + assertEquals(15, h.getNextHiddenBoundary(false, 7)); + assertEquals(15, h.getNextHiddenBoundary(false, 14)); + + // returns same value if there is no next hidden column + assertEquals(22, h.getNextHiddenBoundary(false, 22)); + } + + @Test(groups = "Functional") + public void testIterator() + { + HiddenColumns h = new HiddenColumns(); + Iterator result = h.iterator(); + assertFalse(result.hasNext()); + + h.hideColumns(5, 10); + result = h.iterator(); + int[] next = result.next(); + assertEquals(5, next[0]); + assertEquals(10, next[1]); + assertFalse(result.hasNext()); + + h.hideColumns(22, 23); + result = h.iterator(); + next = result.next(); + assertEquals(5, next[0]); + assertEquals(10, next[1]); + next = result.next(); + assertEquals(22, next[0]); + assertEquals(23, next[1]); + assertFalse(result.hasNext()); + + // test for only one hidden region at start of alignment + ColumnSelection sel = new ColumnSelection(); + h.revealAllHiddenColumns(sel); + h.hideColumns(0, 1); + result = h.iterator(); + next = result.next(); + assertEquals(0, next[0]); + assertEquals(1, next[1]); + assertFalse(result.hasNext()); + } + + /* @Test(groups = "Functional") + public void testGetVisibleSequenceStrings() + { + HiddenColumns h = new HiddenColumns(); + SequenceI seq1 = new Sequence("TEST1", "GALMFWKQESPVICYHRNDT"); + SequenceI seq2 = new Sequence("TEST2", "VICYHRNDTGA"); + SequenceI[] seqs = new SequenceI[2]; + seqs[0] = seq1; + seqs[1] = seq2; + String[] result = h.getVisibleSequenceStrings(5, 10, seqs); + assertEquals(2, result.length); + assertEquals("WKQES", result[0]); + assertEquals("RNDTG", result[1]); + + h.hideColumns(6, 8); + result = h.getVisibleSequenceStrings(5, 10, seqs); + assertEquals(2, result.length); + assertEquals("WS", result[0]); + assertEquals("RG", result[1]); + + SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---"); + ColumnSelection sel = new ColumnSelection(); + h.revealAllHiddenColumns(sel); + h.hideColumns(1, 3); + h.hideColumns(6, 11); + assertEquals("-D", + h.getVisibleSequenceStrings(0, 5, new SequenceI[] + { seq })[0]); + }*/ + + @Test(groups = "Functional") + public void testHideInsertionsFor() + { + HiddenColumns h = new HiddenColumns(); + HiddenColumns h2 = new HiddenColumns(); + SequenceI seq1 = new Sequence("TEST1", "GAL---MFW-KQESPVICY--HRNDT"); + SequenceI seq2 = new Sequence("TEST1", "GALMFWKQESPVICYHRNDT"); + + h.hideList(seq2.getInsertions()); + assertTrue(h.equals(h2)); + assertEquals(0, h.getSize()); + + h.hideList(seq1.getInsertions()); + h2.hideColumns(3, 5); + h2.hideColumns(9, 9); + h2.hideColumns(19, 20); + assertTrue(h.equals(h2)); + assertEquals(6, h.getSize()); + } + + @Test(groups = "Functional") + public void testHideColumns_BitSet_range() + { + HiddenColumns h = new HiddenColumns(); + HiddenColumns h2 = new HiddenColumns(); + + BitSet tohide = new BitSet(25); + h.hideColumns(tohide); + assertTrue(h.equals(h2)); + + // when setting bitset, first param is inclusive, second exclusive + tohide.set(3, 6); + tohide.set(9); + tohide.set(15, 21); + h.clearAndHideColumns(tohide, 5, 23); + + h2.hideColumns(5, 5); + h2.hideColumns(9, 9); + h2.hideColumns(15, 20); + assertTrue(h.equals(h2)); + assertEquals(h.getSize(), h2.getSize()); + + tohide.clear(); + tohide.set(41); + h.clearAndHideColumns(tohide, 23, 30); + assertTrue(h.equals(h2)); + assertEquals(h.getSize(), h2.getSize()); + + tohide.set(41); + h.clearAndHideColumns(tohide, 30, 45); + h2.hideColumns(41, 41); + assertTrue(h.equals(h2)); + assertEquals(h.getSize(), h2.getSize()); + + tohide.clear(); + tohide.set(25, 28); + h.clearAndHideColumns(tohide, 17, 50); + h2 = new HiddenColumns(); + h2.hideColumns(5, 5); + h2.hideColumns(9, 9); + h2.hideColumns(15, 16); + h2.hideColumns(25, 27); + assertTrue(h.equals(h2)); + assertEquals(h.getSize(), h2.getSize()); + + HiddenColumns hc = new HiddenColumns(); + hc.hideColumns(3, 5); + hc.hideColumns(15, 20); + hc.hideColumns(45, 60); + + tohide = new BitSet(); + + // all unhidden if tohide is empty and range covers hidden + hc.clearAndHideColumns(tohide, 1, 70); + assertTrue(!hc.hasHiddenColumns()); + assertEquals(0, hc.getSize()); + + hc.hideColumns(3, 5); + hc.hideColumns(15, 20); + hc.hideColumns(45, 60); + assertEquals(25, hc.getSize()); + + // but not if range does not cover hidden + hc.clearAndHideColumns(tohide, 23, 40); + assertTrue(hc.hasHiddenColumns()); + assertEquals(25, hc.getSize()); + + // and partial unhide if range partially covers + hc.clearAndHideColumns(tohide, 1, 17); + Iterator it = hc.iterator(); + assertTrue(it.hasNext()); + int[] region = it.next(); + + assertEquals(18, region[0]); + assertEquals(20, region[1]); + + assertTrue(it.hasNext()); + region = it.next(); + + assertEquals(45, region[0]); + assertEquals(60, region[1]); + + assertFalse(it.hasNext()); + assertEquals(19, hc.getSize()); + } + + @Test(groups = "Functional") + public void testOffsetByVisibleColumns() + { + HiddenColumns h = new HiddenColumns(); + int result = h.offsetByVisibleColumns(-1, 10); + assertEquals(9, result); + + h.hideColumns(7, 9); + result = h.offsetByVisibleColumns(-4, 10); + assertEquals(3, result); + + h.hideColumns(14, 15); + result = h.offsetByVisibleColumns(-4, 10); + assertEquals(3, result); + + result = h.offsetByVisibleColumns(-10, 17); + assertEquals(2, result); + + result = h.offsetByVisibleColumns(-1, 7); + assertEquals(5, result); + + result = h.offsetByVisibleColumns(-1, 8); + assertEquals(5, result); + + result = h.offsetByVisibleColumns(-3, 15); + assertEquals(10, result); + + ColumnSelection sel = new ColumnSelection(); + h.revealAllHiddenColumns(sel); + h.hideColumns(0, 30); + result = h.offsetByVisibleColumns(-31, 0); + assertEquals(-31, result); + + HiddenColumns cs = new HiddenColumns(); + + // test that without hidden columns, offsetByVisibleColumns returns + // position n to left of provided position + long pos = cs.offsetByVisibleColumns(-3, 10); + assertEquals(7, pos); + + // 0 returns same position + pos = cs.offsetByVisibleColumns(0, 10); + assertEquals(10, pos); + + // overflow to left returns negative number + pos = cs.offsetByVisibleColumns(-3, 0); + assertEquals(-3, pos); + + // test that with hidden columns to left of result column + // behaviour is the same as above + cs.hideColumns(1, 3); + + // position n to left of provided position + pos = cs.offsetByVisibleColumns(-3, 10); + assertEquals(7, pos); + + // 0 returns same position + pos = cs.offsetByVisibleColumns(0, 10); + assertEquals(10, pos); + + // test with one set of hidden columns between start and required position + cs.hideColumns(12, 15); + pos = cs.offsetByVisibleColumns(-8, 17); + assertEquals(5, pos); + + // test with two sets of hidden columns between start and required position + cs.hideColumns(20, 21); + pos = cs.offsetByVisibleColumns(-8, 23); + assertEquals(9, pos); + + // repeat last 2 tests with no hidden columns to left of required position + ColumnSelection colsel = new ColumnSelection(); + cs.revealAllHiddenColumns(colsel); + + // test with one set of hidden columns between start and required position + cs.hideColumns(12, 15); + pos = cs.offsetByVisibleColumns(-8, 17); + assertEquals(5, pos); + + // test with two sets of hidden columns between start and required position + cs.hideColumns(20, 21); + pos = cs.offsetByVisibleColumns(-8, 23); + assertEquals(9, pos); + + // test with right (positive) offsets + + // test that without hidden columns, offsetByVisibleColumns returns + // position n to right of provided position + pos = cs.offsetByVisibleColumns(3, 7); + assertEquals(10, pos); + + // test that with hidden columns to left of result column + // behaviour is the same as above + cs.hideColumns(1, 3); + + // test with one set of hidden columns between start and required position + cs.hideColumns(12, 15); + pos = cs.offsetByVisibleColumns(8, 5); + assertEquals(17, pos); + + // test with two sets of hidden columns between start and required position + cs.hideColumns(20, 21); + pos = cs.offsetByVisibleColumns(8, 9); + assertEquals(23, pos); + + // repeat last 2 tests with no hidden columns to left of required position + colsel = new ColumnSelection(); + cs.revealAllHiddenColumns(colsel); + + // test with one set of hidden columns between start and required position + cs.hideColumns(12, 15); + pos = cs.offsetByVisibleColumns(8, 5); + assertEquals(17, pos); + + // test with two sets of hidden columns between start and required position + cs.hideColumns(20, 21); + pos = cs.offsetByVisibleColumns(8, 9); + assertEquals(23, pos); + } + + @Test(groups = "Functional") + public void testBoundedIterator() + { + HiddenColumns h = new HiddenColumns(); + Iterator it = h.getBoundedIterator(0, 10); + + // no hidden columns = nothing to iterate over + assertFalse(it.hasNext()); + + // [start,end] contains all hidden columns + // all regions are returned + h.hideColumns(3, 10); + h.hideColumns(14, 16); + it = h.getBoundedIterator(0, 20); + assertTrue(it.hasNext()); + int[] next = it.next(); + assertEquals(3, next[0]); + assertEquals(10, next[1]); + next = it.next(); + assertEquals(14, next[0]); + assertEquals(16, next[1]); + assertFalse(it.hasNext()); + + // [start,end] overlaps a region + // 1 region returned + it = h.getBoundedIterator(5, 7); + assertTrue(it.hasNext()); + next = it.next(); + assertEquals(3, next[0]); + assertEquals(10, next[1]); + assertFalse(it.hasNext()); + + // [start,end] fully contains 1 region and start of last + // - 2 regions returned + it = h.getBoundedIterator(3, 15); + assertTrue(it.hasNext()); + next = it.next(); + assertEquals(3, next[0]); + assertEquals(10, next[1]); + next = it.next(); + assertEquals(14, next[0]); + assertEquals(16, next[1]); + assertFalse(it.hasNext()); + + // [start,end] contains end of first region and whole of last region + // - 2 regions returned + it = h.getBoundedIterator(4, 20); + assertTrue(it.hasNext()); + next = it.next(); + assertEquals(3, next[0]); + assertEquals(10, next[1]); + next = it.next(); + assertEquals(14, next[0]); + assertEquals(16, next[1]); + assertFalse(it.hasNext()); + } + + @Test(groups = "Functional") + public void testBoundedStartIterator() + { + HiddenColumns h = new HiddenColumns(); + Iterator it = h.getStartRegionIterator(0, 10); + + // no hidden columns = nothing to iterate over + assertFalse(it.hasNext()); + + // [start,end] contains all hidden columns + // all regions are returned + h.hideColumns(3, 10); + h.hideColumns(14, 16); + it = h.getStartRegionIterator(0, 20); + assertTrue(it.hasNext()); + int next = it.next(); + assertEquals(3, next); + next = it.next(); + assertEquals(6, next); + assertFalse(it.hasNext()); + + // [start,end] does not contain a start of a region + // no regions to iterate over + it = h.getStartRegionIterator(4, 5); + assertFalse(it.hasNext()); + + // [start,end] fully contains 1 region and start of last + // - 2 regions returned + it = h.getStartRegionIterator(3, 7); + assertTrue(it.hasNext()); + next = it.next(); + assertEquals(3, next); + next = it.next(); + assertEquals(6, next); + assertFalse(it.hasNext()); + + // [start,end] contains whole of last region + // - 1 region returned + it = h.getStartRegionIterator(4, 20); + assertTrue(it.hasNext()); + next = it.next(); + assertEquals(6, next); + assertFalse(it.hasNext()); + } + + @Test(groups = "Functional") + public void testVisibleBlocksVisBoundsIterator() + { + HiddenColumns h = new HiddenColumns(); + Iterator regions = h.getVisContigsIterator(0, 31, true); + + // only 1 visible region spanning 0-30 if nothing is hidden + assertTrue(regions.hasNext()); + int[] region = regions.next(); + assertEquals(0, region[0]); + assertEquals(30, region[1]); + assertFalse(regions.hasNext()); + + // hide 1 region in middle + // 2 regions one on either side + // second region boundary accounts for hidden columns + h.hideColumns(10, 15); + regions = h.getVisContigsIterator(0, 31, true); + + assertTrue(regions.hasNext()); + region = regions.next(); + assertEquals(0, region[0]); + assertEquals(9, region[1]); + region = regions.next(); + assertEquals(16, region[0]); + assertEquals(36, region[1]); + assertFalse(regions.hasNext()); + + // single hidden region at left + h = new HiddenColumns(); + h.hideColumns(0, 5); + regions = h.getVisContigsIterator(0, 31, true); + + assertTrue(regions.hasNext()); + region = regions.next(); + assertEquals(6, region[0]); + assertEquals(36, region[1]); + assertFalse(regions.hasNext()); + + // single hidden region at right + h = new HiddenColumns(); + h.hideColumns(27, 30); + regions = h.getVisContigsIterator(0, 31, true); + + assertTrue(regions.hasNext()); + region = regions.next(); + assertEquals(0, region[0]); + assertEquals(26, region[1]); + region = regions.next(); + assertEquals(31, region[0]); + assertEquals(34, region[1]); + assertFalse(regions.hasNext()); + + // hidden region at left + hidden region in middle + h = new HiddenColumns(); + h.hideColumns(0, 5); + h.hideColumns(23, 25); + regions = h.getVisContigsIterator(0, 31, true); + + assertTrue(regions.hasNext()); + region = regions.next(); + assertEquals(6, region[0]); + assertEquals(22, region[1]); + region = regions.next(); + assertEquals(26, region[0]); + assertEquals(39, region[1]); + assertFalse(regions.hasNext()); + + // hidden region at right + hidden region in middle + h = new HiddenColumns(); + h.hideColumns(27, 30); + h.hideColumns(11, 14); + regions = h.getVisContigsIterator(0, 31, true); + + assertTrue(regions.hasNext()); + region = regions.next(); + assertEquals(0, region[0]); + assertEquals(10, region[1]); + region = regions.next(); + assertEquals(15, region[0]); + assertEquals(26, region[1]); + region = regions.next(); + assertEquals(31, region[0]); + assertEquals(38, region[1]); + assertFalse(regions.hasNext()); + + // hidden region at left and right + h = new HiddenColumns(); + h.hideColumns(27, 35); + h.hideColumns(0, 4); + regions = h.getVisContigsIterator(0, 31, true); + + assertTrue(regions.hasNext()); + region = regions.next(); + assertEquals(5, region[0]); + assertEquals(26, region[1]); + region = regions.next(); + assertEquals(36, region[0]); + assertEquals(44, region[1]); + assertFalse(regions.hasNext()); + + // multiple hidden regions + h = new HiddenColumns(); + h.hideColumns(1, 1); + h.hideColumns(3, 5); + h.hideColumns(9, 11); + h.hideColumns(22, 26); + + regions = h.getVisContigsIterator(0, 31, true); + + assertTrue(regions.hasNext()); + region = regions.next(); + assertEquals(0, region[0]); + assertEquals(0, region[1]); + region = regions.next(); + assertEquals(2, region[0]); + assertEquals(2, region[1]); + region = regions.next(); + assertEquals(6, region[0]); + assertEquals(8, region[1]); + region = regions.next(); + assertEquals(12, region[0]); + assertEquals(21, region[1]); + region = regions.next(); + assertEquals(27, region[0]); + assertEquals(42, region[1]); + assertFalse(regions.hasNext()); + } + + /* + * the VisibleColsIterator is tested elsewhere, this just tests that + * it can be retrieved from HiddenColumns + */ + @Test(groups = "Functional") + public void testGetVisibleColsIterator() + { + HiddenColumns h = new HiddenColumns(); + Iterator it = h.getVisibleColsIterator(0, 10); + + assertTrue(it instanceof RangeElementsIterator); + } + + @Test(groups = "Functional") + public void testHashCode() + { + HiddenColumns h = new HiddenColumns(); + h.hideColumns(0, 25); + + int result = h.hashCode(); + assertTrue(result > 0); + + h.hideColumns(30, 50); + assertTrue(h.hashCode() > 0); + assertTrue(result != h.hashCode()); } } diff --git a/test/jalview/datamodel/VisibleColsIteratorTest.java b/test/jalview/datamodel/RangeElementsIteratorTest.java similarity index 84% rename from test/jalview/datamodel/VisibleColsIteratorTest.java rename to test/jalview/datamodel/RangeElementsIteratorTest.java index b2d747b..9d14822 100644 --- a/test/jalview/datamodel/VisibleColsIteratorTest.java +++ b/test/jalview/datamodel/RangeElementsIteratorTest.java @@ -22,12 +22,13 @@ package jalview.datamodel; import static org.testng.Assert.assertTrue; +import java.util.Iterator; import java.util.NoSuchElementException; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -public class VisibleColsIteratorTest +public class RangeElementsIteratorTest { HiddenColumns hiddenCols; @@ -50,11 +51,12 @@ public class VisibleColsIteratorTest @Test(groups = { "Functional" }) public void testHasNextAndNextWithHidden() { - VisibleColsIterator it = new VisibleColsIterator(0, 6, hiddenCols); + Iterator it = hiddenCols.getVisibleColsIterator(0, 6); int count = 0; while (it.hasNext()) { - it.next(); + int result = it.next(); + System.out.println(result); count++; } assertTrue(count == 4, "hasNext() is false after 4 iterations"); @@ -67,8 +69,8 @@ public class VisibleColsIteratorTest @Test(groups = { "Functional" }) public void testHasNextAndNextNoHidden() { - VisibleColsIterator it2 = new VisibleColsIterator(0, 3, - new HiddenColumns()); + HiddenColumns test = new HiddenColumns(); + Iterator it2 = test.getVisibleColsIterator(0, 3); int count = 0; while (it2.hasNext()) { @@ -85,8 +87,7 @@ public class VisibleColsIteratorTest @Test(groups = { "Functional" }) public void testHasNextAndNextStartHidden() { - VisibleColsIterator it3 = new VisibleColsIterator(0, 6, - hiddenColsAtStart); + Iterator it3 = hiddenColsAtStart.getVisibleColsIterator(0, 6); int count = 0; while (it3.hasNext()) { @@ -103,7 +104,7 @@ public class VisibleColsIteratorTest @Test(groups = { "Functional" }) public void testHasNextAndNextEndHidden() { - VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols); + Iterator it4 = hiddenCols.getVisibleColsIterator(0, 4); int count = 0; while (it4.hasNext()) { @@ -123,7 +124,7 @@ public class VisibleColsIteratorTest expectedExceptions = { NoSuchElementException.class }) public void testLastNextWithHidden() throws NoSuchElementException { - VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols); + Iterator it = hiddenCols.getVisibleColsIterator(0, 3); while (it.hasNext()) { it.next(); @@ -140,8 +141,8 @@ public class VisibleColsIteratorTest expectedExceptions = { NoSuchElementException.class }) public void testLastNextNoHidden() throws NoSuchElementException { - VisibleColsIterator it2 = new VisibleColsIterator(0, 3, - new HiddenColumns()); + HiddenColumns test = new HiddenColumns(); + Iterator it2 = test.getVisibleColsIterator(0, 3); while (it2.hasNext()) { it2.next(); @@ -158,8 +159,7 @@ public class VisibleColsIteratorTest expectedExceptions = { NoSuchElementException.class }) public void testLastNextStartHidden() throws NoSuchElementException { - VisibleColsIterator it3 = new VisibleColsIterator(0, 6, - hiddenColsAtStart); + Iterator it3 = hiddenColsAtStart.getVisibleColsIterator(0, 6); while (it3.hasNext()) { it3.next(); @@ -176,7 +176,7 @@ public class VisibleColsIteratorTest expectedExceptions = { NoSuchElementException.class }) public void testLastNextEndHidden() throws NoSuchElementException { - VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols); + Iterator it4 = hiddenCols.getVisibleColsIterator(0, 4); while (it4.hasNext()) { it4.next(); @@ -192,7 +192,7 @@ public class VisibleColsIteratorTest expectedExceptions = { UnsupportedOperationException.class }) public void testRemove() throws UnsupportedOperationException { - VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols); + Iterator it = hiddenCols.getVisibleColsIterator(0, 3); it.remove(); } } diff --git a/test/jalview/datamodel/SequenceFeatureTest.java b/test/jalview/datamodel/SequenceFeatureTest.java index fbeb365..c955979 100644 --- a/test/jalview/datamodel/SequenceFeatureTest.java +++ b/test/jalview/datamodel/SequenceFeatureTest.java @@ -273,4 +273,47 @@ public class SequenceFeatureTest "group"); assertTrue(sf.isContactFeature()); } + + @Test(groups = { "Functional" }) + public void testGetDetailsReport() + { + // single locus, no group, no score + SequenceFeature sf = new SequenceFeature("variant", "G,C", 22, 22, null); + String expected = "
            " + + "" + + "
            Typevariant
            Start/end22
            DescriptionG,C
            "; + assertEquals(expected, sf.getDetailsReport()); + + // contact feature + sf = new SequenceFeature("Disulphide Bond", "a description", 28, 31, + null); + expected = "
            " + + "" + + "
            TypeDisulphide Bond
            Start/end28:31
            Descriptiona description
            "; + assertEquals(expected, sf.getDetailsReport()); + + sf = new SequenceFeature("variant", "G,C", 22, 33, + 12.5f, "group"); + sf.setValue("Parent", "ENSG001"); + sf.setValue("Child", "ENSP002"); + expected = "
            " + + "" + + "" + + "" + + "" + + "" + + "
            Typevariant
            Start/end22-33
            DescriptionG,C
            Score12.5
            Groupgroup
            ChildENSP002
            ParentENSG001
            "; + assertEquals(expected, sf.getDetailsReport()); + + /* + * feature with embedded html link in description + */ + String desc = "Fer2 Status: True Positive Pfam 8_8"; + sf = new SequenceFeature("Pfam", desc, 8, 83, "Uniprot"); + expected = "
            " + + "" + + "" + + "
            TypePfam
            Start/end8-83
            DescriptionFer2 Status: True Positive Pfam 8_8
            GroupUniprot
            "; + assertEquals(expected, sf.getDetailsReport()); + } } diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index 833d957..9629b6f 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -28,6 +28,7 @@ import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertSame; import static org.testng.AssertJUnit.assertTrue; +import jalview.analysis.AlignmentGenerator; import jalview.commands.EditCommand; import jalview.commands.EditCommand.Action; import jalview.datamodel.PDBEntry.Type; @@ -38,16 +39,17 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; +import java.util.Iterator; import java.util.List; import java.util.Vector; -import junit.extensions.PA; - import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import junit.extensions.PA; + public class SequenceTest { @@ -245,7 +247,9 @@ public class SequenceTest sq.sequenceChanged(); assertEquals(6, sq.findIndex(9)); - sq = new Sequence("test/8-13", "-A--B-C-D-E-F--"); + final String aligned = "-A--B-C-D-E-F--"; + assertEquals(15, aligned.length()); + sq = new Sequence("test/8-13", aligned); assertEquals(2, sq.findIndex(8)); sq.sequenceChanged(); assertEquals(5, sq.findIndex(9)); @@ -261,6 +265,29 @@ public class SequenceTest // beyond end returns last residue column sq.sequenceChanged(); assertEquals(13, sq.findIndex(99)); + + /* + * residue before sequence 'end' but beyond end of sequence returns + * length of sequence (last column) (rightly or wrongly!) + */ + sq = new Sequence("test/8-15", "A-B-C-"); // trailing gap case + assertEquals(6, sq.getLength()); + sq.sequenceChanged(); + assertEquals(sq.getLength(), sq.findIndex(14)); + sq = new Sequence("test/8-99", "-A--B-C-D"); // trailing residue case + sq.sequenceChanged(); + assertEquals(sq.getLength(), sq.findIndex(65)); + + /* + * residue after sequence 'start' but before first residue returns + * zero (before first column) (rightly or wrongly!) + */ + sq = new Sequence("test/8-15", "-A-B-C-"); // leading gap case + sq.sequenceChanged(); + assertEquals(0, sq.findIndex(3)); + sq = new Sequence("test/8-15", "A-B-C-"); // leading residue case + sq.sequenceChanged(); + assertEquals(0, sq.findIndex(2)); } @Test(groups = { "Functional" }) @@ -875,7 +902,7 @@ public class SequenceTest Assert.assertEquals(pdbe1a, sq.getDatasetSequence().getPDBEntry("1PDB"), "PDB Entry '1PDB' not found on dataset sequence via getPDBEntry."); - ArrayList annotsList = new ArrayList(); + ArrayList annotsList = new ArrayList<>(); System.out.println(">>>>>> " + sq.getSequenceAsString().length()); annotsList.add(new Annotation("A", "A", 'X', 0.1f)); annotsList.add(new Annotation("A", "A", 'X', 0.1f)); @@ -1476,14 +1503,61 @@ public class SequenceTest { Sequence sq = new Sequence("test/8-13", "-A--BCD-EF--"); - // find F given A + // find F given A, check cursor is now at the found position assertEquals(10, sq.findIndex(13, new SequenceCursor(sq, 8, 2, 0))); + SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor"); + assertEquals(13, cursor.residuePosition); + assertEquals(10, cursor.columnPosition); // find A given F assertEquals(2, sq.findIndex(8, new SequenceCursor(sq, 13, 10, 0))); + cursor = (SequenceCursor) PA.getValue(sq, "cursor"); + assertEquals(8, cursor.residuePosition); + assertEquals(2, cursor.columnPosition); - // find C given C + // find C given C (no cursor update is done for this case) assertEquals(6, sq.findIndex(10, new SequenceCursor(sq, 10, 6, 0))); + SequenceCursor cursor2 = (SequenceCursor) PA.getValue(sq, "cursor"); + assertSame(cursor2, cursor); + + /* + * sequence 'end' beyond end of sequence returns length of sequence + * (for compatibility with pre-cursor code) + * - also verify the cursor is left in a valid state + */ + sq = new Sequence("test/8-99", "-A--B-C-D-E-F--"); // trailing gap case + assertEquals(7, sq.findIndex(10)); // establishes a cursor + cursor = (SequenceCursor) PA.getValue(sq, "cursor"); + assertEquals(10, cursor.residuePosition); + assertEquals(7, cursor.columnPosition); + assertEquals(sq.getLength(), sq.findIndex(65)); + cursor2 = (SequenceCursor) PA.getValue(sq, "cursor"); + assertSame(cursor, cursor2); // not updated for this case! + + sq = new Sequence("test/8-99", "-A--B-C-D-E-F"); // trailing residue case + sq.findIndex(10); // establishes a cursor + cursor = (SequenceCursor) PA.getValue(sq, "cursor"); + assertEquals(sq.getLength(), sq.findIndex(65)); + cursor2 = (SequenceCursor) PA.getValue(sq, "cursor"); + assertSame(cursor, cursor2); // not updated for this case! + + /* + * residue after sequence 'start' but before first residue should return + * zero (for compatibility with pre-cursor code) + */ + sq = new Sequence("test/8-15", "-A-B-C-"); // leading gap case + sq.findIndex(10); // establishes a cursor + cursor = (SequenceCursor) PA.getValue(sq, "cursor"); + assertEquals(0, sq.findIndex(3)); + cursor2 = (SequenceCursor) PA.getValue(sq, "cursor"); + assertSame(cursor, cursor2); // not updated for this case! + + sq = new Sequence("test/8-15", "A-B-C-"); // leading residue case + sq.findIndex(10); // establishes a cursor + cursor = (SequenceCursor) PA.getValue(sq, "cursor"); + assertEquals(0, sq.findIndex(2)); + cursor2 = (SequenceCursor) PA.getValue(sq, "cursor"); + assertSame(cursor, cursor2); // not updated for this case! } @Test(groups = { "Functional" }) @@ -1699,6 +1773,20 @@ public class SequenceTest } @Test(groups = { "Functional" }) + public void testGapBitset() + { + SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--"); + BitSet bs = sq.gapBitset(); + BitSet expected = new BitSet(); + expected.set(0); + expected.set(4, 7); + expected.set(9); + expected.set(11, 13); + + assertTrue(bs.equals(expected)); + + } + public void testFindFeatures_largeEndPos() { /* @@ -1906,4 +1994,162 @@ public class SequenceTest assertEquals(8, sq.getDatasetSequence().getStart()); assertEquals(9, sq.getDatasetSequence().getEnd()); } + + /** + * Test the code used to locate the reference sequence ruler origin + */ + @Test(groups = { "Functional" }) + public void testLocateVisibleStartofSequence() + { + // create random alignment + AlignmentGenerator gen = new AlignmentGenerator(false); + AlignmentI al = gen.generate(50, 20, 123, 5, 5); + + HiddenColumns cs = al.getHiddenColumns(); + ColumnSelection colsel = new ColumnSelection(); + + SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---"); + assertEquals(2, seq.findIndex(seq.getStart())); + + // no hidden columns + assertEquals(seq.findIndex(seq.getStart()) - 1, + seq.firstResidueOutsideIterator(cs.iterator())); + + // hidden column on gap after end of sequence - should not affect bounds + colsel.hideSelectedColumns(13, al.getHiddenColumns()); + assertEquals(seq.findIndex(seq.getStart()) - 1, + seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + // hidden column on gap before beginning of sequence - should vis bounds by + // one + colsel.hideSelectedColumns(0, al.getHiddenColumns()); + assertEquals(seq.findIndex(seq.getStart()) - 2, + cs.absoluteToVisibleColumn( + seq.firstResidueOutsideIterator(cs.iterator()))); + + cs.revealAllHiddenColumns(colsel); + // hide columns around most of sequence - leave one residue remaining + cs.hideColumns(1, 3); + cs.hideColumns(6, 11); + + Iterator it = cs.getVisContigsIterator(0, 6, false); + + assertEquals("-D", seq.getSequenceStringFromIterator(it)); + // cs.getVisibleSequenceStrings(0, 5, new SequenceI[] + // { seq })[0]); + + assertEquals(4, seq.firstResidueOutsideIterator(cs.iterator())); + cs.revealAllHiddenColumns(colsel); + + // hide whole sequence - should just get location of hidden region + // containing sequence + cs.hideColumns(1, 11); + assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 15); + assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator())); + + SequenceI seq2 = new Sequence("RefSeq2", "-------A-SD-ASD--E---"); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(7, 17); + assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(3, 17); + assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(3, 19); + assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 0); + assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 1); + assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 2); + assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(1, 1); + assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(1, 2); + assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(1, 3); + assertEquals(4, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 2); + cs.hideColumns(5, 6); + assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 2); + cs.hideColumns(5, 6); + cs.hideColumns(9, 10); + assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 2); + cs.hideColumns(7, 11); + assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(2, 4); + cs.hideColumns(7, 11); + assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(2, 4); + cs.hideColumns(7, 12); + assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(1, 11); + assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 12); + assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 4); + cs.hideColumns(6, 12); + assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 1); + cs.hideColumns(3, 12); + assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(3, 14); + cs.hideColumns(17, 19); + assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(3, 7); + cs.hideColumns(9, 14); + cs.hideColumns(17, 19); + assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator())); + + cs.revealAllHiddenColumns(colsel); + cs.hideColumns(0, 1); + cs.hideColumns(3, 4); + cs.hideColumns(6, 8); + cs.hideColumns(10, 12); + assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator())); + + } } diff --git a/test/jalview/datamodel/StartRegionIteratorTest.java b/test/jalview/datamodel/StartRegionIteratorTest.java new file mode 100644 index 0000000..23d0b00 --- /dev/null +++ b/test/jalview/datamodel/StartRegionIteratorTest.java @@ -0,0 +1,214 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import static org.testng.Assert.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.testng.annotations.Test; + +public class StartRegionIteratorTest +{ + /** + * Test the start region iterator + */ + @Test(groups = { "Functional" }) + public void testBasicBoundsIterator() + { + List hiddenColumns = null; + + // null hidden columns + Iterator it = new StartRegionIterator(3, 10, + hiddenColumns); + assertFalse(it.hasNext()); + + hiddenColumns = new ArrayList<>(); + + // no hidden columns + it = new StartRegionIterator(3, 10, hiddenColumns); + assertFalse(it.hasNext()); + + // add some hidden columns + hiddenColumns.add(new int[] { 5, 10 }); + hiddenColumns.add(new int[] { 25, 40 }); + + it = new StartRegionIterator(3, 10, hiddenColumns); + assertTrue(it.hasNext()); + Integer result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(3, 15, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(3, 18, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(3, 19, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(19, (int) result); + assertFalse(it.hasNext()); + + hiddenColumns.add(new int[] { 47, 50 }); + + it = new StartRegionIterator(15, 60, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(19, (int) result); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(25, (int) result); + assertFalse(it.hasNext()); + } + + /** + * Test the start region iterator with null cursor + */ + @Test(groups = { "Functional" }) + public void testBoundsIteratorUsingNullCursor() + { + List hiddenColumns = null; + HiddenCursorPosition pos = null; + + // null hidden columns + Iterator it = new StartRegionIterator(pos, 3, 10, + hiddenColumns); + assertFalse(it.hasNext()); + + hiddenColumns = new ArrayList<>(); + + // no hidden columns + it = new StartRegionIterator(pos, 3, 10, hiddenColumns); + assertFalse(it.hasNext()); + + // add some hidden columns + hiddenColumns.add(new int[] { 5, 10 }); + hiddenColumns.add(new int[] { 25, 40 }); + + it = new StartRegionIterator(pos, 3, 10, hiddenColumns); + assertTrue(it.hasNext()); + Integer result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(pos, 3, 15, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(pos, 3, 18, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(pos, 3, 19, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(19, (int) result); + assertFalse(it.hasNext()); + + hiddenColumns.add(new int[] { 47, 50 }); + + it = new StartRegionIterator(pos, 15, 60, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(19, (int) result); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(25, (int) result); + assertFalse(it.hasNext()); + } + + /** + * Test the start region iterator with nonnull cursor + */ + @Test(groups = { "Functional" }) + public void testBoundsIteratorUsingCursor() + { + List hiddenColumns = new ArrayList<>(); + + // add some hidden columns + hiddenColumns.add(new int[] { 5, 10 }); + hiddenColumns.add(new int[] { 25, 40 }); + + HiddenCursorPosition pos = new HiddenCursorPosition(0, 0); + + Iterator it = new StartRegionIterator(pos, 3, 10, + hiddenColumns); + assertTrue(it.hasNext()); + Integer result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(pos, 3, 15, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(pos, 3, 18, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertFalse(it.hasNext()); + + it = new StartRegionIterator(pos, 3, 19, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(5, (int) result); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(19, (int) result); + assertFalse(it.hasNext()); + + pos = new HiddenCursorPosition(1, 6); + hiddenColumns.add(new int[] { 47, 50 }); + + it = new StartRegionIterator(pos, 15, 60, hiddenColumns); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(19, (int) result); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(25, (int) result); + assertFalse(it.hasNext()); + } +} diff --git a/test/jalview/datamodel/VisibleContigsIteratorTest.java b/test/jalview/datamodel/VisibleContigsIteratorTest.java new file mode 100644 index 0000000..8f31dae --- /dev/null +++ b/test/jalview/datamodel/VisibleContigsIteratorTest.java @@ -0,0 +1,223 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.datamodel; + +import static org.testng.Assert.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +public class VisibleContigsIteratorTest +{ + /** + * Test the iterator with single visible regions + */ + @Test(groups = { "Functional" }) + public void testSimpleVisibleRegions() + { + List hiddenColumns = null; + + // null hidden columns + VisibleContigsIterator it = new VisibleContigsIterator(3, 10, + hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + int[] result = it.next(); + assertEquals(3, result[0]); + assertEquals(9, result[1]); + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + + hiddenColumns = new ArrayList<>(); + + // no hidden columns + it = new VisibleContigsIterator(3, 10, + hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(3, result[0]); + assertEquals(9, result[1]); + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + + // hidden columns, but not where we are looking + hiddenColumns.add(new int[] { 5, 10 }); + hiddenColumns.add(new int[] { 25, 40 }); + + it = new VisibleContigsIterator(2, 3, hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(2, result[0]); + assertEquals(2, result[1]); + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + + it = new VisibleContigsIterator(5, 7, hiddenColumns); + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + + it = new VisibleContigsIterator(11, 15, hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(11, result[0]); + assertEquals(14, result[1]); + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + + it = new VisibleContigsIterator(50, 60, hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(50, result[0]); + assertEquals(59, result[1]); + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + } + + /** + * Test the iterator with multiple visible regions + */ + @Test(groups = { "Functional" }) + public void testMultipleVisibleRegions() + { + List hiddenColumns = new ArrayList<>(); + hiddenColumns.add(new int[] { 5, 10 }); + hiddenColumns.add(new int[] { 25, 40 }); + + // all hidden columns covered + VisibleContigsIterator it = new VisibleContigsIterator(3, 50, + hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + int[] result = it.next(); + assertEquals(3, result[0]); + assertEquals(4, result[1]); + + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(11, result[0]); + assertEquals(24, result[1]); + + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(41, result[0]); + assertEquals(49, result[1]); + + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + } + + /** + * Test the iterator with regions which start/end at hidden region edges + */ + @Test(groups = { "Functional" }) + public void testVisibleRegionsAtHiddenEdges() + { + List hiddenColumns = new ArrayList<>(); + hiddenColumns.add(new int[] { 5, 10 }); + hiddenColumns.add(new int[] { 25, 40 }); + + VisibleContigsIterator it = new VisibleContigsIterator(0, 10, + hiddenColumns); + assertTrue(it.hasNext()); + assertTrue(it.endsAtHidden()); + int[] result = it.next(); + assertEquals(0, result[0]); + assertEquals(4, result[1]); + assertFalse(it.hasNext()); + assertTrue(it.endsAtHidden()); + + it = new VisibleContigsIterator(2, 11, hiddenColumns); + assertTrue(it.hasNext()); + assertTrue(it.endsAtHidden()); + result = it.next(); + assertEquals(2, result[0]); + assertEquals(4, result[1]); + assertFalse(it.hasNext()); + assertTrue(it.endsAtHidden()); + + it = new VisibleContigsIterator(2, 12, hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(2, result[0]); + assertEquals(4, result[1]); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(11, result[0]); + assertEquals(11, result[1]); + assertFalse(it.hasNext()); + assertFalse(it.endsAtHidden()); + + it = new VisibleContigsIterator(13, 25, hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(13, result[0]); + assertEquals(24, result[1]); + assertFalse(it.hasNext()); + + it = new VisibleContigsIterator(13, 26, hiddenColumns); + assertTrue(it.hasNext()); + assertTrue(it.endsAtHidden()); + result = it.next(); + assertEquals(13, result[0]); + assertEquals(24, result[1]); + assertFalse(it.hasNext()); + + it = new VisibleContigsIterator(13, 27, hiddenColumns); + assertTrue(it.hasNext()); + assertTrue(it.endsAtHidden()); + result = it.next(); + assertEquals(13, result[0]); + assertEquals(24, result[1]); + assertFalse(it.hasNext()); + + it = new VisibleContigsIterator(13, 41, hiddenColumns); + assertTrue(it.hasNext()); + assertTrue(it.endsAtHidden()); + result = it.next(); + assertEquals(13, result[0]); + assertEquals(24, result[1]); + assertFalse(it.hasNext()); + + it = new VisibleContigsIterator(13, 42, hiddenColumns); + assertTrue(it.hasNext()); + assertFalse(it.endsAtHidden()); + result = it.next(); + assertEquals(13, result[0]); + assertEquals(24, result[1]); + assertTrue(it.hasNext()); + result = it.next(); + assertEquals(41, result[0]); + assertEquals(41, result[1]); + } +} diff --git a/test/jalview/datamodel/features/FeatureAttributesTest.java b/test/jalview/datamodel/features/FeatureAttributesTest.java new file mode 100644 index 0000000..0846ec2 --- /dev/null +++ b/test/jalview/datamodel/features/FeatureAttributesTest.java @@ -0,0 +1,133 @@ +package jalview.datamodel.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.features.FeatureAttributes.Datatype; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import junit.extensions.PA; + +public class FeatureAttributesTest +{ + + /** + * clear down attributes map before tests + */ + @BeforeClass(alwaysRun = true) + public void setUp() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + ((Map) PA.getValue(fa, "attributes")).clear(); + } + + /** + * clear down attributes map after tests + */ + @AfterMethod(alwaysRun = true) + public void tearDown() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + ((Map) PA.getValue(fa, "attributes")).clear(); + } + + /** + * Test the method that keeps attribute names in non-case-sensitive order, + * including handling of 'compound' names + */ + @Test(groups="Functional") + public void testAttributeNameComparator() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + Comparator comp = (Comparator) PA.getValue(fa, + "comparator"); + + assertEquals( + comp.compare(new String[] { "CSQ" }, new String[] { "csq" }), 0); + + assertTrue(comp.compare(new String[] { "CSQ", "a" }, + new String[] { "csq" }) > 0); + + assertTrue(comp.compare(new String[] { "CSQ" }, new String[] { "csq", + "b" }) < 0); + + assertTrue(comp.compare(new String[] { "CSQ", "AF" }, new String[] { + "csq", "ac" }) > 0); + + assertTrue(comp.compare(new String[] { "CSQ", "ac" }, new String[] { + "csq", "AF" }) < 0); + } + + @Test(groups = "Functional") + public void testGetMinMax() + { + SequenceFeature sf = new SequenceFeature("Pfam", "desc", 10, 20, + "group"); + FeatureAttributes fa = FeatureAttributes.getInstance(); + assertNull(fa.getMinMax("Pfam", "kd")); + sf.setValue("domain", "xyz"); + assertNull(fa.getMinMax("Pfam", "kd")); + sf.setValue("kd", "1.3"); + assertEquals(fa.getMinMax("Pfam", "kd"), new float[] { 1.3f, 1.3f }); + sf.setValue("kd", "-2.6"); + assertEquals(fa.getMinMax("Pfam", "kd"), new float[] { -2.6f, 1.3f }); + // setting 'mixed' character and numeric values wipes the min/max value + sf.setValue("kd", "some text"); + assertNull(fa.getMinMax("Pfam", "kd")); + + Map csq = new HashMap<>(); + csq.put("AF", "-3"); + sf.setValue("CSQ", csq); + assertEquals(fa.getMinMax("Pfam", "CSQ", "AF"), + new float[] + { -3f, -3f }); + csq.put("AF", "4"); + sf.setValue("CSQ", csq); + assertEquals(fa.getMinMax("Pfam", "CSQ", "AF"), + new float[] + { -3f, 4f }); + } + + /** + * Test the method that returns an attribute description, provided it is + * recorded and unique + */ + @Test(groups = "Functional") + public void testGetDescription() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + // with no description returns null + assertNull(fa.getDescription("Pfam", "kd")); + // with a unique description, returns that value + fa.addDescription("Pfam", "desc1", "kd"); + assertEquals(fa.getDescription("Pfam", "kd"), "desc1"); + // with ambiguous description, returns null + fa.addDescription("Pfam", "desc2", "kd"); + assertNull(fa.getDescription("Pfam", "kd")); + } + + @Test(groups = "Functional") + public void testDatatype() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + assertNull(fa.getDatatype("Pfam", "kd")); + SequenceFeature sf = new SequenceFeature("Pfam", "desc", 10, 20, + "group"); + sf.setValue("kd", "-1"); + sf.setValue("domain", "Metal"); + sf.setValue("phase", "1"); + sf.setValue("phase", "reverse"); + assertEquals(fa.getDatatype("Pfam", "kd"), Datatype.Number); + assertEquals(fa.getDatatype("Pfam", "domain"), Datatype.Character); + assertEquals(fa.getDatatype("Pfam", "phase"), Datatype.Mixed); + } +} diff --git a/test/jalview/datamodel/features/FeatureMatcherSetTest.java b/test/jalview/datamodel/features/FeatureMatcherSetTest.java new file mode 100644 index 0000000..a2d2c9a --- /dev/null +++ b/test/jalview/datamodel/features/FeatureMatcherSetTest.java @@ -0,0 +1,419 @@ +package jalview.datamodel.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import jalview.datamodel.SequenceFeature; +import jalview.util.matcher.Condition; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import org.testng.annotations.Test; + +public class FeatureMatcherSetTest +{ + @Test(groups = "Functional") + public void testMatches_byAttribute() + { + /* + * a numeric matcher - MatcherTest covers more conditions + */ + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2", + "AF"); + FeatureMatcherSetI fms = new FeatureMatcherSet(); + fms.and(fm); + SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp"); + assertFalse(fms.matches(sf)); + sf.setValue("AF", "foobar"); + assertFalse(fms.matches(sf)); + sf.setValue("AF", "-2"); + assertTrue(fms.matches(sf)); + sf.setValue("AF", "-1"); + assertTrue(fms.matches(sf)); + sf.setValue("AF", "-3"); + assertFalse(fms.matches(sf)); + sf.setValue("AF", ""); + assertFalse(fms.matches(sf)); + + /* + * a string pattern matcher + */ + fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "AF"); + fms = new FeatureMatcherSet(); + fms.and(fm); + assertFalse(fms.matches(sf)); + sf.setValue("AF", "raining cats and dogs"); + assertTrue(fms.matches(sf)); + } + + @Test(groups = "Functional") + public void testAnd() + { + // condition1: AF value contains "dog" (matches) + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains, + "dog", "AF"); + // condition 2: CSQ value does not contain "how" (does not match) + FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains, + "how", "CSQ"); + + SequenceFeature sf = new SequenceFeature("Cath", "helix domain", 11, 12, + 6.2f, "grp"); + sf.setValue("AF", "raining cats and dogs"); + sf.setValue("CSQ", "showers"); + + assertTrue(fm1.matches(sf)); + assertFalse(fm2.matches(sf)); + + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass + fms.and(fm1); + assertTrue(fms.matches(sf)); + fms.and(fm2); + assertFalse(fms.matches(sf)); + + /* + * OR a failed attribute condition with a matched label condition + */ + fms = new FeatureMatcherSet(); + fms.and(fm2); + assertFalse(fms.matches(sf)); + FeatureMatcher byLabelPass = FeatureMatcher.byLabel(Condition.Contains, + "Helix"); + fms.or(byLabelPass); + assertTrue(fms.matches(sf)); + + /* + * OR a failed attribute condition with a failed score condition + */ + fms = new FeatureMatcherSet(); + fms.and(fm2); + assertFalse(fms.matches(sf)); + FeatureMatcher byScoreFail = FeatureMatcher.byScore(Condition.LT, + "5.9"); + fms.or(byScoreFail); + assertFalse(fms.matches(sf)); + + /* + * OR failed attribute and score conditions with matched label condition + */ + fms = new FeatureMatcherSet(); + fms.or(fm2); + fms.or(byScoreFail); + assertFalse(fms.matches(sf)); + fms.or(byLabelPass); + assertTrue(fms.matches(sf)); + } + + @Test(groups = "Functional") + public void testToString() + { + Locale.setDefault(Locale.ENGLISH); + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.LT, "1.2", + "AF"); + assertEquals(fm1.toString(), "AF < 1.2"); + + FeatureMatcher fm2 = FeatureMatcher.byAttribute(Condition.NotContains, + "path", "CLIN_SIG"); + assertEquals(fm2.toString(), "CLIN_SIG does not contain 'path'"); + + /* + * AND them + */ + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertEquals(fms.toString(), ""); + fms.and(fm1); + assertEquals(fms.toString(), "AF < 1.2"); + fms.and(fm2); + assertEquals(fms.toString(), + "(AF < 1.2) and (CLIN_SIG does not contain 'path')"); + + /* + * OR them + */ + fms = new FeatureMatcherSet(); + assertEquals(fms.toString(), ""); + fms.or(fm1); + assertEquals(fms.toString(), "AF < 1.2"); + fms.or(fm2); + assertEquals(fms.toString(), + "(AF < 1.2) or (CLIN_SIG does not contain 'path')"); + + try + { + fms.and(fm1); + fail("Expected exception"); + } catch (IllegalStateException e) + { + // expected + } + } + + @Test(groups = "Functional") + public void testOr() + { + // condition1: AF value contains "dog" (matches) + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains, + "dog", "AF"); + // condition 2: CSQ value does not contain "how" (does not match) + FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains, + "how", "CSQ"); + + SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp"); + sf.setValue("AF", "raining cats and dogs"); + sf.setValue("CSQ", "showers"); + + assertTrue(fm1.matches(sf)); + assertFalse(fm2.matches(sf)); + + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass + fms.or(fm1); + assertTrue(fms.matches(sf)); + fms.or(fm2); + assertTrue(fms.matches(sf)); // true or false makes true + + fms = new FeatureMatcherSet(); + fms.or(fm2); + assertFalse(fms.matches(sf)); + fms.or(fm1); + assertTrue(fms.matches(sf)); // false or true makes true + + try + { + fms.and(fm2); + fail("Expected exception"); + } catch (IllegalStateException e) + { + // expected + } + } + + @Test(groups = "Functional") + public void testIsEmpty() + { + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2.0", + "AF"); + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertTrue(fms.isEmpty()); + fms.and(fm); + assertFalse(fms.isEmpty()); + } + + @Test(groups = "Functional") + public void testGetMatchers() + { + FeatureMatcherSetI fms = new FeatureMatcherSet(); + + /* + * empty iterable: + */ + Iterator iterator = fms.getMatchers().iterator(); + assertFalse(iterator.hasNext()); + + /* + * one matcher: + */ + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.GE, "-2", + "AF"); + fms.and(fm1); + iterator = fms.getMatchers().iterator(); + assertSame(fm1, iterator.next()); + assertFalse(iterator.hasNext()); + + /* + * two matchers: + */ + FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.LT, "8f", + "AF"); + fms.and(fm2); + iterator = fms.getMatchers().iterator(); + assertSame(fm1, iterator.next()); + assertSame(fm2, iterator.next()); + assertFalse(iterator.hasNext()); + } + + /** + * Tests for the 'compound attribute' key i.e. where first key's value is a map + * from which we take the value for the second key, e.g. CSQ : Consequence + */ + @Test(groups = "Functional") + public void testMatches_compoundKey() + { + /* + * a numeric matcher - MatcherTest covers more conditions + */ + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2", + "CSQ", "Consequence"); + SequenceFeature sf = new SequenceFeature("Cath", "desc", 2, 10, "grp"); + FeatureMatcherSetI fms = new FeatureMatcherSet(); + fms.and(fm); + assertFalse(fms.matches(sf)); + Map csq = new HashMap<>(); + sf.setValue("CSQ", csq); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "-2"); + assertTrue(fms.matches(sf)); + csq.put("Consequence", "-1"); + assertTrue(fms.matches(sf)); + csq.put("Consequence", "-3"); + assertFalse(fms.matches(sf)); + csq.put("Consequence", ""); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "junk"); + assertFalse(fms.matches(sf)); + + /* + * a string pattern matcher + */ + fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "CSQ", + "Consequence"); + fms = new FeatureMatcherSet(); + fms.and(fm); + assertFalse(fms.matches(sf)); + csq.put("PolyPhen", "damaging"); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "damaging"); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "Catastrophic"); + assertTrue(fms.matches(sf)); + } + + /** + * Tests for toStableString which (unlike toString) does not i18n the + * conditions + * + * @see FeatureMatcherTest#testToStableString() + */ + @Test(groups = "Functional") + public void testToStableString() + { + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.LT, "1.2", + "AF"); + assertEquals(fm1.toStableString(), "AF LT 1.2"); + + FeatureMatcher fm2 = FeatureMatcher.byAttribute(Condition.NotContains, + "path", "CLIN_SIG"); + assertEquals(fm2.toStableString(), "CLIN_SIG NotContains path"); + + /* + * AND them + */ + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertEquals(fms.toStableString(), ""); + fms.and(fm1); + // no brackets needed if a single condition + assertEquals(fms.toStableString(), "AF LT 1.2"); + // brackets if more than one condition + fms.and(fm2); + assertEquals(fms.toStableString(), + "(AF LT 1.2) AND (CLIN_SIG NotContains path)"); + + /* + * OR them + */ + fms = new FeatureMatcherSet(); + assertEquals(fms.toStableString(), ""); + fms.or(fm1); + assertEquals(fms.toStableString(), "AF LT 1.2"); + fms.or(fm2); + assertEquals(fms.toStableString(), + "(AF LT 1.2) OR (CLIN_SIG NotContains path)"); + + /* + * attribute or value including space is quoted + */ + FeatureMatcher fm3 = FeatureMatcher.byAttribute(Condition.NotMatches, + "foo bar", "CSQ", "Poly Phen"); + assertEquals(fm3.toStableString(), + "'CSQ:Poly Phen' NotMatches 'foo bar'"); + fms.or(fm3); + assertEquals(fms.toStableString(), + "(AF LT 1.2) OR (CLIN_SIG NotContains path) OR ('CSQ:Poly Phen' NotMatches 'foo bar')"); + + try + { + fms.and(fm1); + fail("Expected exception"); + } catch (IllegalStateException e) + { + // expected + } + } + + /** + * Tests for parsing a string representation of a FeatureMatcherSet + * + * @see FeatureMatcherSetTest#testToStableString() + */ + @Test(groups = "Functional") + public void testFromString() + { + String descriptor = "AF LT 1.2"; + FeatureMatcherSetI fms = FeatureMatcherSet.fromString(descriptor); + + /* + * shortcut asserts by verifying a 'roundtrip', + * which we trust if other tests pass :-) + */ + assertEquals(fms.toStableString(), descriptor); + + // brackets optional, quotes optional, condition case insensitive + fms = FeatureMatcherSet.fromString("('AF' lt '1.2')"); + assertEquals(fms.toStableString(), descriptor); + + descriptor = "(AF LT 1.2) AND (CLIN_SIG NotContains path)"; + fms = FeatureMatcherSet.fromString(descriptor); + assertEquals(fms.toStableString(), descriptor); + + // AND is not case-sensitive + fms = FeatureMatcherSet + .fromString("(AF LT 1.2) and (CLIN_SIG NotContains path)"); + assertEquals(fms.toStableString(), descriptor); + + descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path)"; + fms = FeatureMatcherSet.fromString(descriptor); + assertEquals(fms.toStableString(), descriptor); + + // OR is not case-sensitive + fms = FeatureMatcherSet + .fromString("(AF LT 1.2) or (CLIN_SIG NotContains path)"); + assertEquals(fms.toStableString(), descriptor); + + // can get away without brackets on last match condition + fms = FeatureMatcherSet + .fromString("(AF LT 1.2) or CLIN_SIG NotContains path"); + assertEquals(fms.toStableString(), descriptor); + + descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path) OR ('CSQ:Poly Phen' NotMatches 'foo bar')"; + fms = FeatureMatcherSet.fromString(descriptor); + assertEquals(fms.toStableString(), descriptor); + + // can't mix OR and AND + descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path) AND ('CSQ:Poly Phen' NotMatches 'foo bar')"; + assertNull(FeatureMatcherSet.fromString(descriptor)); + + // can't mix AND and OR + descriptor = "(AF LT 1.2) and (CLIN_SIG NotContains path) or ('CSQ:Poly Phen' NotMatches 'foo bar')"; + assertNull(FeatureMatcherSet.fromString(descriptor)); + + // brackets missing + assertNull(FeatureMatcherSet + .fromString("AF LT 1.2 or CLIN_SIG NotContains path")); + + // invalid conjunction + assertNull(FeatureMatcherSet.fromString("(AF LT 1.2) but (AF GT -2)")); + + // unbalanced quote (1) + assertNull(FeatureMatcherSet.fromString("('AF lt '1.2')")); + + // unbalanced quote (2) + assertNull(FeatureMatcherSet.fromString("('AF' lt '1.2)")); + } +} diff --git a/test/jalview/datamodel/features/FeatureMatcherTest.java b/test/jalview/datamodel/features/FeatureMatcherTest.java new file mode 100644 index 0000000..4bd34cb --- /dev/null +++ b/test/jalview/datamodel/features/FeatureMatcherTest.java @@ -0,0 +1,352 @@ +package jalview.datamodel.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import jalview.datamodel.SequenceFeature; +import jalview.util.MessageManager; +import jalview.util.matcher.Condition; + +import java.util.Locale; + +import org.testng.annotations.Test; + +public class FeatureMatcherTest +{ + @Test(groups = "Functional") + public void testMatches_byLabel() + { + SequenceFeature sf = new SequenceFeature("Cath", "this is my label", 11, + 12, "grp"); + + /* + * contains - not case sensitive + */ + assertTrue( + FeatureMatcher.byLabel(Condition.Contains, "IS").matches(sf)); + assertTrue(FeatureMatcher.byLabel(Condition.Contains, "").matches(sf)); + assertFalse( + FeatureMatcher.byLabel(Condition.Contains, "ISNT").matches(sf)); + + /* + * does not contain + */ + assertTrue(FeatureMatcher.byLabel(Condition.NotContains, "isnt") + .matches(sf)); + assertFalse(FeatureMatcher.byLabel(Condition.NotContains, "is") + .matches(sf)); + + /* + * matches + */ + assertTrue(FeatureMatcher.byLabel(Condition.Matches, "THIS is MY label") + .matches(sf)); + assertFalse(FeatureMatcher.byLabel(Condition.Matches, "THIS is MY") + .matches(sf)); + + /* + * does not match + */ + assertFalse(FeatureMatcher + .byLabel(Condition.NotMatches, "THIS is MY label").matches(sf)); + assertTrue(FeatureMatcher.byLabel(Condition.NotMatches, "THIS is MY") + .matches(sf)); + + /* + * is present / not present + */ + assertTrue(FeatureMatcher.byLabel(Condition.Present, "").matches(sf)); + assertFalse( + FeatureMatcher.byLabel(Condition.NotPresent, "").matches(sf)); + } + + @Test(groups = "Functional") + public void testMatches_byScore() + { + SequenceFeature sf = new SequenceFeature("Cath", "this is my label", 11, + 12, 3.2f, "grp"); + + assertTrue(FeatureMatcher.byScore(Condition.LT, "3.3").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.LT, "3.2").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.LT, "2.2").matches(sf)); + + assertTrue(FeatureMatcher.byScore(Condition.LE, "3.3").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.LE, "3.2").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.LE, "2.2").matches(sf)); + + assertFalse(FeatureMatcher.byScore(Condition.EQ, "3.3").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.EQ, "3.2").matches(sf)); + + assertFalse(FeatureMatcher.byScore(Condition.GE, "3.3").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.GE, "3.2").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.GE, "2.2").matches(sf)); + + assertFalse(FeatureMatcher.byScore(Condition.GT, "3.3").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.GT, "3.2").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.GT, "2.2").matches(sf)); + } + + @Test(groups = "Functional") + public void testMatches_byAttribute() + { + /* + * a numeric matcher - MatcherTest covers more conditions + */ + FeatureMatcherI fm = FeatureMatcher + .byAttribute(Condition.GE, "-2", "AF"); + SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", "foobar"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", "-2"); + assertTrue(fm.matches(sf)); + sf.setValue("AF", "-1"); + assertTrue(fm.matches(sf)); + sf.setValue("AF", "-3"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", ""); + assertFalse(fm.matches(sf)); + + /* + * a string pattern matcher + */ + fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "AF"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", "raining cats and dogs"); + assertTrue(fm.matches(sf)); + + fm = FeatureMatcher.byAttribute(Condition.Present, "", "AC"); + assertFalse(fm.matches(sf)); + sf.setValue("AC", "21"); + assertTrue(fm.matches(sf)); + + fm = FeatureMatcher.byAttribute(Condition.NotPresent, "", "AC_Females"); + assertTrue(fm.matches(sf)); + sf.setValue("AC_Females", "21"); + assertFalse(fm.matches(sf)); + } + + @Test(groups = "Functional") + public void testToString() + { + Locale.setDefault(Locale.ENGLISH); + + /* + * toString uses the i18n translation of the enum conditions + */ + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.LT, "1.2", + "AF"); + assertEquals(fm.toString(), "AF < 1.2"); + + /* + * Present / NotPresent omit the value pattern + */ + fm = FeatureMatcher.byAttribute(Condition.Present, "", "AF"); + assertEquals(fm.toString(), "AF is present"); + fm = FeatureMatcher.byAttribute(Condition.NotPresent, "", "AF"); + assertEquals(fm.toString(), "AF is not present"); + + /* + * by Label + */ + fm = FeatureMatcher.byLabel(Condition.Matches, "foobar"); + assertEquals(fm.toString(), + MessageManager.getString("label.label") + " matches 'foobar'"); + + /* + * by Score + */ + fm = FeatureMatcher.byScore(Condition.GE, "12.2"); + assertEquals(fm.toString(), + MessageManager.getString("label.score") + " >= 12.2"); + } + + @Test(groups = "Functional") + public void testGetAttribute() + { + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2", + "AF"); + assertEquals(fm.getAttribute(), new String[] { "AF" }); + + /* + * compound key (attribute / subattribute) + */ + fm = FeatureMatcher.byAttribute(Condition.GE, "-2F", "CSQ", + "Consequence"); + assertEquals(fm.getAttribute(), new String[] { "CSQ", "Consequence" }); + + /* + * answers null if match is by Label or by Score + */ + assertNull(FeatureMatcher.byLabel(Condition.NotContains, "foo") + .getAttribute()); + assertNull(FeatureMatcher.byScore(Condition.LE, "-1").getAttribute()); + } + + @Test(groups = "Functional") + public void testIsByAttribute() + { + assertFalse(FeatureMatcher.byLabel(Condition.NotContains, "foo") + .isByAttribute()); + assertFalse(FeatureMatcher.byScore(Condition.LE, "-1").isByAttribute()); + assertTrue(FeatureMatcher.byAttribute(Condition.LE, "-1", "AC") + .isByAttribute()); + } + + @Test(groups = "Functional") + public void testIsByLabel() + { + assertTrue(FeatureMatcher.byLabel(Condition.NotContains, "foo") + .isByLabel()); + assertFalse(FeatureMatcher.byScore(Condition.LE, "-1").isByLabel()); + assertFalse(FeatureMatcher.byAttribute(Condition.LE, "-1", "AC") + .isByLabel()); + } + + @Test(groups = "Functional") + public void testIsByScore() + { + assertFalse(FeatureMatcher.byLabel(Condition.NotContains, "foo") + .isByScore()); + assertTrue(FeatureMatcher.byScore(Condition.LE, "-1").isByScore()); + assertFalse(FeatureMatcher.byAttribute(Condition.LE, "-1", "AC") + .isByScore()); + } + + @Test(groups = "Functional") + public void testGetMatcher() + { + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2f", + "AF"); + assertEquals(fm.getMatcher().getCondition(), Condition.GE); + assertEquals(fm.getMatcher().getFloatValue(), -2F); + assertEquals(fm.getMatcher().getPattern(), "-2.0"); + } + + @Test(groups = "Functional") + public void testFromString() + { + FeatureMatcherI fm = FeatureMatcher.fromString("'AF' LT 1.2"); + assertFalse(fm.isByLabel()); + assertFalse(fm.isByScore()); + assertEquals(fm.getAttribute(), new String[] { "AF" }); + assertSame(Condition.LT, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getFloatValue(), 1.2f); + assertEquals(fm.getMatcher().getPattern(), "1.2"); + + // quotes are optional, condition is not case sensitive + fm = FeatureMatcher.fromString("AF lt '1.2'"); + assertFalse(fm.isByLabel()); + assertFalse(fm.isByScore()); + assertEquals(fm.getAttribute(), new String[] { "AF" }); + assertSame(Condition.LT, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getFloatValue(), 1.2f); + assertEquals(fm.getMatcher().getPattern(), "1.2"); + + fm = FeatureMatcher.fromString("'AF' Present"); + assertFalse(fm.isByLabel()); + assertFalse(fm.isByScore()); + assertEquals(fm.getAttribute(), new String[] { "AF" }); + assertSame(Condition.Present, fm.getMatcher().getCondition()); + + fm = FeatureMatcher.fromString("CSQ:Consequence contains damaging"); + assertFalse(fm.isByLabel()); + assertFalse(fm.isByScore()); + assertEquals(fm.getAttribute(), new String[] { "CSQ", "Consequence" }); + assertSame(Condition.Contains, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getPattern(), "damaging"); + + // keyword Label is not case sensitive + fm = FeatureMatcher.fromString("LABEL Matches 'foobar'"); + assertTrue(fm.isByLabel()); + assertFalse(fm.isByScore()); + assertNull(fm.getAttribute()); + assertSame(Condition.Matches, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getPattern(), "foobar"); + + fm = FeatureMatcher.fromString("'Label' matches 'foo bar'"); + assertTrue(fm.isByLabel()); + assertFalse(fm.isByScore()); + assertNull(fm.getAttribute()); + assertSame(Condition.Matches, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getPattern(), "foo bar"); + + // quotes optional on pattern + fm = FeatureMatcher.fromString("'Label' matches foo bar"); + assertTrue(fm.isByLabel()); + assertFalse(fm.isByScore()); + assertNull(fm.getAttribute()); + assertSame(Condition.Matches, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getPattern(), "foo bar"); + + fm = FeatureMatcher.fromString("Score GE 12.2"); + assertFalse(fm.isByLabel()); + assertTrue(fm.isByScore()); + assertNull(fm.getAttribute()); + assertSame(Condition.GE, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getPattern(), "12.2"); + assertEquals(fm.getMatcher().getFloatValue(), 12.2f); + + // keyword Score is not case sensitive + fm = FeatureMatcher.fromString("'SCORE' ge '12.2'"); + assertFalse(fm.isByLabel()); + assertTrue(fm.isByScore()); + assertNull(fm.getAttribute()); + assertSame(Condition.GE, fm.getMatcher().getCondition()); + assertEquals(fm.getMatcher().getPattern(), "12.2"); + assertEquals(fm.getMatcher().getFloatValue(), 12.2f); + + // invalid numeric pattern + assertNull(FeatureMatcher.fromString("Score eq twelve")); + // unbalanced opening quote + assertNull(FeatureMatcher.fromString("'Score ge 12.2")); + // unbalanced pattern quote + assertNull(FeatureMatcher.fromString("'Score' ge '12.2")); + // pattern missing + assertNull(FeatureMatcher.fromString("Score ge")); + // condition and pattern missing + assertNull(FeatureMatcher.fromString("Score")); + // everything missing + assertNull(FeatureMatcher.fromString("")); + } + + /** + * Tests for toStableString which (unlike toString) does not i18n the + * conditions + */ + @Test(groups = "Functional") + public void testToStableString() + { + // attribute name not quoted unless it contains space + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.LT, "1.2", + "AF"); + assertEquals(fm.toStableString(), "AF LT 1.2"); + + /* + * Present / NotPresent omit the value pattern + */ + fm = FeatureMatcher.byAttribute(Condition.Present, "", "AF"); + assertEquals(fm.toStableString(), "AF Present"); + fm = FeatureMatcher.byAttribute(Condition.NotPresent, "", "AF"); + assertEquals(fm.toStableString(), "AF NotPresent"); + + /* + * by Label + * pattern not quoted unless it contains space + */ + fm = FeatureMatcher.byLabel(Condition.Matches, "foobar"); + assertEquals(fm.toStableString(), "Label Matches foobar"); + + fm = FeatureMatcher.byLabel(Condition.Matches, "foo bar"); + assertEquals(fm.toStableString(), "Label Matches 'foo bar'"); + + /* + * by Score + */ + fm = FeatureMatcher.byScore(Condition.GE, "12.2"); + assertEquals(fm.toStableString(), "Score GE 12.2"); + } +} diff --git a/test/jalview/datamodel/features/SequenceFeaturesTest.java b/test/jalview/datamodel/features/SequenceFeaturesTest.java index bdf2cab..29e76bb 100644 --- a/test/jalview/datamodel/features/SequenceFeaturesTest.java +++ b/test/jalview/datamodel/features/SequenceFeaturesTest.java @@ -13,10 +13,10 @@ import java.util.List; import java.util.Map; import java.util.Set; -import junit.extensions.PA; - import org.testng.annotations.Test; +import junit.extensions.PA; + public class SequenceFeaturesTest { @Test(groups = "Functional") @@ -1005,33 +1005,44 @@ public class SequenceFeaturesTest assertTrue(store.getFeaturesByOntology(new String[] {}).isEmpty()); assertTrue(store.getFeaturesByOntology((String[]) null).isEmpty()); - SequenceFeature sf1 = new SequenceFeature("transcript", "desc", 10, 20, + SequenceFeature transcriptFeature = new SequenceFeature("transcript", "desc", 10, 20, Float.NaN, null); - store.add(sf1); + store.add(transcriptFeature); - // mRNA isA transcript; added here 'as if' non-positional - // just to show that non-positional features are included in results - SequenceFeature sf2 = new SequenceFeature("mRNA", "desc", 0, 0, + /* + * mRNA is a sub-type of transcript; added here 'as if' non-positional + * just to show that non-positional features are included in results + */ + SequenceFeature mrnaFeature = new SequenceFeature("mRNA", "desc", 0, 0, Float.NaN, null); - store.add(sf2); + store.add(mrnaFeature); - SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 30, 40, + SequenceFeature pfamFeature = new SequenceFeature("Pfam", "desc", 30, 40, Float.NaN, null); - store.add(sf3); + store.add(pfamFeature); + /* + * "transcript" matches both itself and the sub-term "mRNA" + */ features = store.getFeaturesByOntology("transcript"); assertEquals(features.size(), 2); - assertTrue(features.contains(sf1)); - assertTrue(features.contains(sf2)); + assertTrue(features.contains(transcriptFeature)); + assertTrue(features.contains(mrnaFeature)); + /* + * "mRNA" matches itself but not parent term "transcript" + */ features = store.getFeaturesByOntology("mRNA"); assertEquals(features.size(), 1); - assertTrue(features.contains(sf2)); + assertTrue(features.contains(mrnaFeature)); + /* + * "pfam" is not an SO term but is included as an exact match + */ features = store.getFeaturesByOntology("mRNA", "Pfam"); assertEquals(features.size(), 2); - assertTrue(features.contains(sf2)); - assertTrue(features.contains(sf3)); + assertTrue(features.contains(mrnaFeature)); + assertTrue(features.contains(pfamFeature)); features = store.getFeaturesByOntology("sequence_variant"); assertTrue(features.isEmpty()); @@ -1040,7 +1051,7 @@ public class SequenceFeaturesTest @Test(groups = "Functional") public void testSortFeatures() { - List sfs = new ArrayList(); + List sfs = new ArrayList<>(); SequenceFeature sf1 = new SequenceFeature("Pfam", "desc", 30, 80, Float.NaN, null); sfs.add(sf1); diff --git a/test/jalview/ext/ensembl/EnsemblCdnaTest.java b/test/jalview/ext/ensembl/EnsemblCdnaTest.java index 779962c..c9d8deb 100644 --- a/test/jalview/ext/ensembl/EnsemblCdnaTest.java +++ b/test/jalview/ext/ensembl/EnsemblCdnaTest.java @@ -25,6 +25,7 @@ import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertTrue; +import jalview.datamodel.Sequence; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; @@ -241,37 +242,51 @@ public class EnsemblCdnaTest * accession id as parent */ @Test(groups = "Functional") - public void testIdentifiesSequence() + public void testGetIdentifyingFeatures() { String accId = "ABC123"; - EnsemblCdna testee = new EnsemblCdna(); + SequenceI seq = new Sequence(accId, "MKLNFRQIE"); - // exon with no parent not valid - SequenceFeature sf = new SequenceFeature("exon", "", 1, 2, 0f, null); - assertFalse(testee.identifiesSequence(sf, accId)); + // exon with no parent: not valid + SequenceFeature sf1 = new SequenceFeature("exon", "", 1, 2, 0f, null); + seq.addSequenceFeature(sf1); - // exon with wrong parent not valid - sf.setValue("Parent", "transcript:XYZ"); - assertFalse(testee.identifiesSequence(sf, accId)); + // exon with wrong parent: not valid + SequenceFeature sf2 = new SequenceFeature("exon", "", 1, 2, 0f, null); + sf2.setValue("Parent", "transcript:XYZ"); + seq.addSequenceFeature(sf2); // exon with right parent is valid - sf.setValue("Parent", "transcript:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf3 = new SequenceFeature("exon", "", 1, 2, 0f, null); + sf3.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf3); // exon sub-type with right parent is valid - sf = new SequenceFeature("coding_exon", "", 1, 2, 0f, null); - sf.setValue("Parent", "transcript:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf4 = new SequenceFeature("coding_exon", "", 1, 2, 0f, + null); + sf4.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf4); // transcript not valid: - sf = new SequenceFeature("transcript", "", 1, 2, 0f, null); - sf.setValue("Parent", "transcript:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf5 = new SequenceFeature("transcript", "", 1, 2, 0f, + null); + sf5.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf5); // CDS not valid: - sf = new SequenceFeature("CDS", "", 1, 2, 0f, null); - sf.setValue("Parent", "transcript:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf6 = new SequenceFeature("transcript", "", 1, 2, 0f, + null); + sf6.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf6); + + List sfs = new EnsemblCdna() + .getIdentifyingFeatures(seq, accId); + assertFalse(sfs.contains(sf1)); + assertFalse(sfs.contains(sf2)); + assertTrue(sfs.contains(sf3)); + assertTrue(sfs.contains(sf4)); + assertFalse(sfs.contains(sf5)); + assertFalse(sfs.contains(sf6)); } @Test(groups = "Functional") diff --git a/test/jalview/ext/ensembl/EnsemblCdsTest.java b/test/jalview/ext/ensembl/EnsemblCdsTest.java index 8482c90..a44ab7f 100644 --- a/test/jalview/ext/ensembl/EnsemblCdsTest.java +++ b/test/jalview/ext/ensembl/EnsemblCdsTest.java @@ -24,6 +24,7 @@ import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; +import jalview.datamodel.Sequence; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; @@ -152,37 +153,50 @@ public class EnsemblCdsTest * accession id as parent */ @Test(groups = "Functional") - public void testIdentifiesSequence() + public void testGetIdentifyingFeatures() { String accId = "ABC123"; - EnsemblCds testee = new EnsemblCds(); + SequenceI seq = new Sequence(accId, "MKDONS"); // cds with no parent not valid - SequenceFeature sf = new SequenceFeature("CDS", "", 1, 2, 0f, null); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf1 = new SequenceFeature("CDS", "", 1, 2, 0f, null); + seq.addSequenceFeature(sf1); // cds with wrong parent not valid - sf.setValue("Parent", "transcript:XYZ"); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf2 = new SequenceFeature("CDS", "", 1, 2, 0f, null); + sf2.setValue("Parent", "transcript:XYZ"); + seq.addSequenceFeature(sf2); // cds with right parent is valid - sf.setValue("Parent", "transcript:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf3 = new SequenceFeature("CDS", "", 1, 2, 0f, null); + sf3.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf3); // cds sub-type with right parent is valid - sf = new SequenceFeature("CDS_predicted", "", 1, 2, 0f, null); - sf.setValue("Parent", "transcript:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf4 = new SequenceFeature("CDS_predicted", "", 1, 2, 0f, + null); + sf4.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf4); // transcript not valid: - sf = new SequenceFeature("transcript", "", 1, 2, 0f, null); - sf.setValue("Parent", "transcript:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf5 = new SequenceFeature("transcript", "", 1, 2, 0f, + null); + sf5.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf5); // exon not valid: - sf = new SequenceFeature("exon", "", 1, 2, 0f, null); - sf.setValue("Parent", "transcript:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf6 = new SequenceFeature("exon", "", 1, 2, 0f, null); + sf6.setValue("Parent", "transcript:" + accId); + seq.addSequenceFeature(sf6); + + List sfs = new EnsemblCds().getIdentifyingFeatures(seq, + accId); + assertFalse(sfs.contains(sf1)); + assertFalse(sfs.contains(sf2)); + assertTrue(sfs.contains(sf3)); + assertTrue(sfs.contains(sf4)); + assertFalse(sfs.contains(sf5)); + assertFalse(sfs.contains(sf6)); } @Test(groups = "Functional") diff --git a/test/jalview/ext/ensembl/EnsemblGeneTest.java b/test/jalview/ext/ensembl/EnsemblGeneTest.java index 1b1a2b4..446b4f7 100644 --- a/test/jalview/ext/ensembl/EnsemblGeneTest.java +++ b/test/jalview/ext/ensembl/EnsemblGeneTest.java @@ -25,6 +25,8 @@ import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import jalview.api.FeatureSettingsModelI; +import jalview.bin.Cache; +import jalview.datamodel.Sequence; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; @@ -53,6 +55,7 @@ public class EnsemblGeneTest @BeforeClass(alwaysRun = true) public void setUp() { + Cache.loadProperties("test/jalview/io/testProps.jvprops"); SequenceOntologyFactory.setInstance(new SequenceOntologyLite()); } @@ -75,17 +78,9 @@ public class EnsemblGeneTest genomic.setEnd(50000); String geneId = "ABC123"; - // gene at (start+20000) length 501 - // should be ignored - the first 'gene' found defines the whole range - // (note features are found in position order, not addition order) - SequenceFeature sf = new SequenceFeature("gene", "", 20000, 20500, 0f, - null); - sf.setValue("ID", "gene:" + geneId); - sf.setStrand("+"); - genomic.addSequenceFeature(sf); - // gene at (start + 10500) length 101 - sf = new SequenceFeature("gene", "", 10500, 10600, 0f, null); + SequenceFeature sf = new SequenceFeature("gene", "", 10500, 10600, 0f, + null); sf.setValue("ID", "gene:" + geneId); sf.setStrand("+"); genomic.addSequenceFeature(sf); @@ -115,17 +110,9 @@ public class EnsemblGeneTest genomic.setEnd(50000); String geneId = "ABC123"; - // gene at (start+20000) length 501 - // should be ignored - the first 'gene' found defines the whole range - // (real data would only have one such feature) - SequenceFeature sf = new SequenceFeature("ncRNA_gene", "", 20000, - 20500, 0f, null); - sf.setValue("ID", "gene:" + geneId); - sf.setStrand("-"); - genomic.addSequenceFeature(sf); - // gene at (start + 10500) length 101 - sf = new SequenceFeature("gene", "", 10500, 10600, 0f, null); + SequenceFeature sf = new SequenceFeature("gene", "", 10500, 10600, 0f, + null); sf.setValue("ID", "gene:" + geneId); sf.setStrand("+"); genomic.addSequenceFeature(sf); @@ -238,40 +225,48 @@ public class EnsemblGeneTest * accession id as ID */ @Test(groups = "Functional") - public void testIdentifiesSequence() + public void testGetIdentifyingFeatures() { String accId = "ABC123"; - EnsemblGene testee = new EnsemblGene(); + SequenceI seq = new Sequence(accId, "HIBEES"); // gene with no ID not valid - SequenceFeature sf = new SequenceFeature("gene", "", 1, 2, 0f, null); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf1 = new SequenceFeature("gene", "", 1, 2, 0f, null); + seq.addSequenceFeature(sf1); // gene with wrong ID not valid - sf.setValue("ID", "gene:XYZ"); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf2 = new SequenceFeature("gene", "", 1, 2, 0f, null); + sf2.setValue("ID", "gene:XYZ"); + seq.addSequenceFeature(sf2); // gene with right ID is valid - sf.setValue("ID", "gene:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf3 = new SequenceFeature("gene", "", 1, 2, 0f, null); + sf3.setValue("ID", "gene:" + accId); + seq.addSequenceFeature(sf3); // gene sub-type with right ID is valid - sf = new SequenceFeature("snRNA_gene", "", 1, 2, 0f, null); - sf.setValue("ID", "gene:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); - - // test is not case-sensitive - assertTrue(testee.identifiesSequence(sf, accId.toLowerCase())); + SequenceFeature sf4 = new SequenceFeature("snRNA_gene", "", 1, 2, 0f, null); + sf4.setValue("ID", "gene:" + accId); + seq.addSequenceFeature(sf4); // transcript not valid: - sf = new SequenceFeature("transcript", "", 1, 2, 0f, null); - sf.setValue("ID", "gene:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf5 = new SequenceFeature("transcript", "", 1, 2, 0f, null); + sf5.setValue("ID", "gene:" + accId); + seq.addSequenceFeature(sf5); // exon not valid: - sf = new SequenceFeature("exon", "", 1, 2, 0f, null); - sf.setValue("ID", "gene:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf6 = new SequenceFeature("exon", "", 1, 2, 0f, null); + sf6.setValue("ID", "gene:" + accId); + seq.addSequenceFeature(sf6); + + List sfs = new EnsemblGene() + .getIdentifyingFeatures(seq, accId); + assertFalse(sfs.contains(sf1)); + assertFalse(sfs.contains(sf2)); + assertTrue(sfs.contains(sf3)); + assertTrue(sfs.contains(sf4)); + assertFalse(sfs.contains(sf5)); + assertFalse(sfs.contains(sf6)); } /** diff --git a/test/jalview/ext/ensembl/EnsemblGenomeTest.java b/test/jalview/ext/ensembl/EnsemblGenomeTest.java index 8687da9..72ee492 100644 --- a/test/jalview/ext/ensembl/EnsemblGenomeTest.java +++ b/test/jalview/ext/ensembl/EnsemblGenomeTest.java @@ -24,6 +24,7 @@ import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; +import jalview.datamodel.Sequence; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; @@ -162,43 +163,58 @@ public class EnsemblGenomeTest * accession id as ID */ @Test(groups = "Functional") - public void testIdentifiesSequence() + public void testGetIdentifyingFeatures() { String accId = "ABC123"; - EnsemblGenome testee = new EnsemblGenome(); + SequenceI seq = new Sequence(accId, "HEARTS"); // transcript with no ID not valid - SequenceFeature sf = new SequenceFeature("transcript", "", 1, 2, 0f, + SequenceFeature sf1 = new SequenceFeature("transcript", "", 1, 2, 0f, null); - assertFalse(testee.identifiesSequence(sf, accId)); + seq.addSequenceFeature(sf1); // transcript with wrong ID not valid - sf.setValue("ID", "transcript"); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf2 = new SequenceFeature("transcript", "", 1, 2, 0f, + null); + sf2.setValue("ID", "transcript"); + seq.addSequenceFeature(sf2); // transcript with right ID is valid - sf.setValue("ID", "transcript:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf3 = new SequenceFeature("transcript", "", 1, 2, 0f, + null); + sf3.setValue("ID", "transcript:" + accId); + seq.addSequenceFeature(sf3); // transcript sub-type with right ID is valid - sf = new SequenceFeature("ncRNA", "", 1, 2, 0f, null); - sf.setValue("ID", "transcript:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf4 = new SequenceFeature("ncRNA", "", 1, 2, 0f, null); + sf4.setValue("ID", "transcript:" + accId); + seq.addSequenceFeature(sf4); // Ensembl treats NMD_transcript_variant as if a transcript - sf = new SequenceFeature("NMD_transcript_variant", "", 1, 2, 0f, null); - sf.setValue("ID", "transcript:" + accId); - assertTrue(testee.identifiesSequence(sf, accId)); + SequenceFeature sf5 = new SequenceFeature("NMD_transcript_variant", "", + 1, 2, 0f, null); + sf5.setValue("ID", "transcript:" + accId); + seq.addSequenceFeature(sf5); // gene not valid: - sf = new SequenceFeature("gene", "", 1, 2, 0f, null); - sf.setValue("ID", "transcript:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf6 = new SequenceFeature("gene", "", 1, 2, 0f, null); + sf6.setValue("ID", "transcript:" + accId); + seq.addSequenceFeature(sf6); // exon not valid: - sf = new SequenceFeature("exon", "", 1, 2, 0f, null); - sf.setValue("ID", "transcript:" + accId); - assertFalse(testee.identifiesSequence(sf, accId)); + SequenceFeature sf7 = new SequenceFeature("exon", "", 1, 2, 0f, null); + sf7.setValue("ID", "transcript:" + accId); + seq.addSequenceFeature(sf7); + + List sfs = new EnsemblGenome() + .getIdentifyingFeatures(seq, accId); + assertFalse(sfs.contains(sf1)); + assertFalse(sfs.contains(sf2)); + assertTrue(sfs.contains(sf3)); + assertTrue(sfs.contains(sf4)); + assertTrue(sfs.contains(sf5)); + assertFalse(sfs.contains(sf6)); + assertFalse(sfs.contains(sf7)); } } diff --git a/test/jalview/ext/ensembl/EnsemblRestClientTest.java b/test/jalview/ext/ensembl/EnsemblRestClientTest.java index cc3a3db..460d16c 100644 --- a/test/jalview/ext/ensembl/EnsemblRestClientTest.java +++ b/test/jalview/ext/ensembl/EnsemblRestClientTest.java @@ -79,19 +79,6 @@ public class EnsemblRestClientTest { return false; } - - @Override - protected String getRequestMimeType(boolean b) - { - return null; - } - - @Override - protected String getResponseMimeType() - { - return null; - } - }; } diff --git a/test/jalview/ext/ensembl/EnsemblSeqProxyAdapter.java b/test/jalview/ext/ensembl/EnsemblSeqProxyAdapter.java index 9fad30e..be7bdf2 100644 --- a/test/jalview/ext/ensembl/EnsemblSeqProxyAdapter.java +++ b/test/jalview/ext/ensembl/EnsemblSeqProxyAdapter.java @@ -21,6 +21,10 @@ package jalview.ext.ensembl; import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; + +import java.util.ArrayList; +import java.util.List; /** * A convenience class to simplify writing unit tests (pending Mockito or @@ -65,9 +69,10 @@ public class EnsemblSeqProxyAdapter extends EnsemblSeqProxy } @Override - protected boolean identifiesSequence(SequenceFeature sf, String accId) + protected List getIdentifyingFeatures(SequenceI seq, + String accId) { - return false; + return new ArrayList<>(); } } diff --git a/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java b/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java index e2af26b..69b2ad4 100644 --- a/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java +++ b/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java @@ -21,18 +21,15 @@ package jalview.ext.ensembl; import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertSame; -import static org.testng.AssertJUnit.assertTrue; -import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.datamodel.features.SequenceFeatures; import jalview.gui.JvOptionPane; import jalview.io.DataSourceType; import jalview.io.FastaFile; -import jalview.io.FileParse; import jalview.io.gff.SequenceOntologyFactory; import jalview.io.gff.SequenceOntologyLite; @@ -127,7 +124,11 @@ public class EnsemblSeqProxyTest + "LKKALMMRGLIPECCAVYRIQDGEKKPIGWDTDISWLTGEELHVEVLENVPLTTHNFVRK\n" + "TFFTLAFCDFCRKLLFQGFRCQTCGYKFHQRCSTEVPLMCVNYDQLDLLFVSKFFEHHPI\n" + "PQEEASLAETALTSGSSPSAPASDSIGPQILTSPSPSKSIPIPQPFRPADEDHRNQFGQR\n" - + "DRSSSAPNVHINTIEPVNIDDLIRDQGFRGDGGSTTGLSATPPASLPGSLTNVKALQKSP\n" + + "DRSSSAPNVHINTIEPVNIDDLIRDQGFRGDG\n" + // ? insertion added in ENSP00000288602.11, not in P15056 + + "APLNQLMRCLRKYQSRTPSPLLHSVPSEIVFDFEPGPVFR\n" + // end insertion + + "GSTTGLSATPPASLPGSLTNVKALQKSP\n" + "GPQRERKSSSSSEDRNRMKTLGRRDSSDDWEIPDGQITVGQRIGSGSFGTVYKGKWHGDV\n" + "AVKMLNVTAPTPQQLQAFKNEVGVLRKTRHVNILLFMGYSTKPQLAIVTQWCEGSSLYHH\n" + "LHIIETKFEMIKLIDIARQTAQGMDYLHAKSIIHRDLKSNNIFLHEDLTVKIGDFGLATV\n" @@ -155,22 +156,21 @@ public class EnsemblSeqProxyTest } @Test(dataProvider = "ens_seqs", suiteName = "live") - public void testGetOneSeqs(EnsemblRestClient proxy, String sq, + public void testGetSequenceRecords(EnsemblSeqProxy proxy, String sq, String fastasq) throws Exception { - FileParse fp = proxy.getSequenceReader(Arrays - .asList(new String[] { sq })); - SequenceI[] sqs = new FastaFile(fp).getSeqsAsArray(); FastaFile trueRes = new FastaFile(fastasq, DataSourceType.PASTE); - SequenceI[] trueSqs = trueRes.getSeqsAsArray(); - Assert.assertEquals(sqs.length, trueSqs.length, + SequenceI[] expected = trueRes.getSeqsAsArray(); + AlignmentI retrieved = proxy.getSequenceRecords(sq); + + Assert.assertEquals(retrieved.getHeight(), expected.length, "Different number of sequences retrieved for query " + sq); - Alignment ral = new Alignment(sqs); - for (SequenceI tr : trueSqs) + + for (SequenceI tr : expected) { SequenceI[] rseq; Assert.assertNotNull( - rseq = ral.findSequenceMatch(tr.getName()), + rseq = retrieved.findSequenceMatch(tr.getName()), "Couldn't find sequences matching expected sequence " + tr.getName()); Assert.assertEquals(rseq.length, 1, @@ -181,7 +181,6 @@ public class EnsemblSeqProxyTest "Sequences differ for " + tr.getName() + "\n" + "Exp:" + tr.getSequenceAsString() + "\n" + "Got:" + rseq[0].getSequenceAsString()); - } } diff --git a/test/jalview/ext/htsjdk/TestHtsContigDb.java b/test/jalview/ext/htsjdk/TestHtsContigDb.java index 350b599..28c5cf0 100644 --- a/test/jalview/ext/htsjdk/TestHtsContigDb.java +++ b/test/jalview/ext/htsjdk/TestHtsContigDb.java @@ -20,13 +20,19 @@ */ package jalview.ext.htsjdk; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + import jalview.datamodel.SequenceI; -import jalview.gui.JvOptionPane; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** @@ -35,25 +41,108 @@ import org.testng.annotations.Test; */ public class TestHtsContigDb { + @Test(groups = "Functional") + public final void testGetSequenceProxy() throws Exception + { + String pathname = "test/jalview/ext/htsjdk/pgmb.fasta"; + HtsContigDb db = new HtsContigDb("ADB", new File(pathname)); + + assertTrue(db.isValid()); + assertTrue(db.isIndexed()); // htsjdk opens the .fai file + + SequenceI sq = db.getSequenceProxy("Deminut"); + assertNotNull(sq); + assertEquals(sq.getLength(), 606); + + /* + * read a sequence earlier in the file + */ + sq = db.getSequenceProxy("PPL_06716"); + assertNotNull(sq); + assertEquals(sq.getLength(), 602); + + // dict = db.getDictionary(f, truncate)) + } - @BeforeClass(alwaysRun = true) - public void setUpJvOptionPane() + /** + * Trying to open a .fai file directly results in IllegalArgumentException - + * have to provide the unindexed file name instead + */ + @Test( + groups = "Functional", + expectedExceptions = java.lang.IllegalArgumentException.class) + public final void testGetSequenceProxy_indexed() { - JvOptionPane.setInteractiveMode(false); - JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); + String pathname = "test/jalview/ext/htsjdk/pgmb.fasta.fai"; + new HtsContigDb("ADB", new File(pathname)); + fail("Expected exception opening .fai file"); } + /** + * Tests that exercise + *
              + *
            • opening an unindexed fasta file
            • + *
            • creating a .fai index
            • + *
            • opening the fasta file, now using the index
            • + *
            • error on creating index if overwrite not allowed
            • + *
            + * + * @throws IOException + */ @Test(groups = "Functional") - public final void testHTSReferenceSequence() throws Exception + public void testCreateFastaSequenceIndex() throws IOException { - HtsContigDb remmadb = new HtsContigDb("REEMADB", new File( - "test/jalview/ext/htsjdk/pgmb.fasta")); + File fasta = new File("test/jalview/ext/htsjdk/pgmb.fasta"); + + /* + * create .fai with no overwrite fails if it exists + */ + try { + HtsContigDb.createFastaSequenceIndex(fasta.toPath(), false); + fail("Expected exception"); + } catch (IOException e) + { + // expected + } - Assert.assertTrue(remmadb.isValid()); + /* + * create a copy of the .fasta (as a temp file) + */ + File copyFasta = File.createTempFile("copyFasta", ".fasta"); + copyFasta.deleteOnExit(); + assertTrue(copyFasta.exists()); + Files.copy(fasta.toPath(), copyFasta.toPath(), + StandardCopyOption.REPLACE_EXISTING); - SequenceI sq = remmadb.getSequenceProxy("Deminut"); - Assert.assertNotNull(sq); - Assert.assertNotEquals(0, sq.getLength()); + /* + * open the Fasta file - not indexed, as no .fai file yet exists + */ + HtsContigDb db = new HtsContigDb("ADB", copyFasta); + assertTrue(db.isValid()); + assertFalse(db.isIndexed()); + db.close(); + + /* + * create the .fai index, re-open the .fasta file - now indexed + */ + HtsContigDb.createFastaSequenceIndex(copyFasta.toPath(), true); + db = new HtsContigDb("ADB", copyFasta); + assertTrue(db.isValid()); + assertTrue(db.isIndexed()); + db.close(); } + /** + * A convenience 'test' that may be run to create a .fai file for any given + * fasta file + * + * @throws IOException + */ + @Test(enabled = false) + public void testCreateIndex() throws IOException + { + + File fasta = new File("test/jalview/io/vcf/contigs.fasta"); + HtsContigDb.createFastaSequenceIndex(fasta.toPath(), true); + } } diff --git a/test/jalview/ext/htsjdk/VCFReaderTest.java b/test/jalview/ext/htsjdk/VCFReaderTest.java new file mode 100644 index 0000000..bf617ae --- /dev/null +++ b/test/jalview/ext/htsjdk/VCFReaderTest.java @@ -0,0 +1,200 @@ +package jalview.ext.htsjdk; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import htsjdk.samtools.util.CloseableIterator; +import htsjdk.variant.variantcontext.Allele; +import htsjdk.variant.variantcontext.VariantContext; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +import org.testng.annotations.Test; + +public class VCFReaderTest +{ + private static final String[] VCF = new String[] { + "##fileformat=VCFv4.2", + "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO", + "20\t3\t.\tC\tG\t.\tPASS\tDP=100", // SNP C/G + "20\t7\t.\tG\tGA\t.\tPASS\tDP=100", // insertion G/GA + "18\t2\t.\tACG\tA\t.\tPASS\tDP=100" }; // deletion ACG/A + + // gnomAD exome variant dataset + private static final String VCF_PATH = "/Volumes/gjb/smacgowan/NOBACK/resources/gnomad/gnomad.exomes.r2.0.1.sites.vcf.gz"; + + // "https://storage.cloud.google.com/gnomad-public/release/2.0.1/vcf/exomes/gnomad.exomes.r2.0.1.sites.vcf.gz"; + + /** + * A test to exercise some basic functionality of the htsjdk VCF reader, + * reading from a non-index VCF file + * + * @throws IOException + */ + @Test(groups = "Functional") + public void testReadVcf_plain() throws IOException + { + File f = writeVcfFile(); + VCFReader reader = new VCFReader(f.getAbsolutePath()); + CloseableIterator variants = reader.iterator(); + + /* + * SNP C/G variant + */ + VariantContext vc = variants.next(); + assertTrue(vc.isSNP()); + Allele ref = vc.getReference(); + assertEquals(ref.getBaseString(), "C"); + List alleles = vc.getAlleles(); + assertEquals(alleles.size(), 2); + assertTrue(alleles.get(0).isReference()); + assertEquals(alleles.get(0).getBaseString(), "C"); + assertFalse(alleles.get(1).isReference()); + assertEquals(alleles.get(1).getBaseString(), "G"); + + /* + * Insertion G -> GA + */ + vc = variants.next(); + assertFalse(vc.isSNP()); + assertTrue(vc.isSimpleInsertion()); + ref = vc.getReference(); + assertEquals(ref.getBaseString(), "G"); + alleles = vc.getAlleles(); + assertEquals(alleles.size(), 2); + assertTrue(alleles.get(0).isReference()); + assertEquals(alleles.get(0).getBaseString(), "G"); + assertFalse(alleles.get(1).isReference()); + assertEquals(alleles.get(1).getBaseString(), "GA"); + + /* + * Deletion ACG -> A + */ + vc = variants.next(); + assertFalse(vc.isSNP()); + assertTrue(vc.isSimpleDeletion()); + ref = vc.getReference(); + assertEquals(ref.getBaseString(), "ACG"); + alleles = vc.getAlleles(); + assertEquals(alleles.size(), 2); + assertTrue(alleles.get(0).isReference()); + assertEquals(alleles.get(0).getBaseString(), "ACG"); + assertFalse(alleles.get(1).isReference()); + assertEquals(alleles.get(1).getBaseString(), "A"); + + assertFalse(variants.hasNext()); + + variants.close(); + reader.close(); + } + + /** + * Creates a temporary file to be read by the htsjdk VCF reader + * + * @return + * @throws IOException + */ + protected File writeVcfFile() throws IOException + { + File f = File.createTempFile("Test", "vcf"); + f.deleteOnExit(); + PrintWriter pw = new PrintWriter(f); + for (String vcfLine : VCF) { + pw.println(vcfLine); + } + pw.close(); + return f; + } + + /** + * A 'test' that demonstrates querying an indexed VCF file for features in a + * specified interval + * + * @throws IOException + */ + @Test + public void testQuery_indexed() throws IOException + { + /* + * if not specified, assumes index file is filename.tbi + */ + VCFReader reader = new VCFReader(VCF_PATH); + + /* + * gene NMT1 (human) is on chromosome 17 + * GCHR38 (Ensembl): 45051610-45109016 + * GCHR37 (gnoMAD): 43128978-43186384 + * CDS begins at offset 9720, first CDS variant at offset 9724 + */ + CloseableIterator features = reader.query("17", + 43128978 + 9724, 43128978 + 9734); // first 11 CDS positions + + assertEquals(printNext(features), 43138702); + assertEquals(printNext(features), 43138704); + assertEquals(printNext(features), 43138707); + assertEquals(printNext(features), 43138708); + assertEquals(printNext(features), 43138710); + assertEquals(printNext(features), 43138711); + assertFalse(features.hasNext()); + + features.close(); + reader.close(); + } + + /** + * Prints the toString value of the next variant, and returns its start + * location + * + * @param features + * @return + */ + protected int printNext(CloseableIterator features) + { + VariantContext next = features.next(); + System.out.println(next.toString()); + return next.getStart(); + } + + // "https://storage.cloud.google.com/gnomad-public/release/2.0.1/vcf/exomes/gnomad.exomes.r2.0.1.sites.vcf.gz"; + + /** + * Test the query method that wraps a non-indexed VCF file + * + * @throws IOException + */ + @Test(groups = "Functional") + public void testQuery_plain() throws IOException + { + File f = writeVcfFile(); + VCFReader reader = new VCFReader(f.getAbsolutePath()); + + /* + * query for overlap of 5-8 - should find variant at 7 + */ + CloseableIterator variants = reader.query("20", 5, 8); + + /* + * INDEL G/GA variant + */ + VariantContext vc = variants.next(); + assertTrue(vc.isIndel()); + assertEquals(vc.getStart(), 7); + assertEquals(vc.getEnd(), 7); + Allele ref = vc.getReference(); + assertEquals(ref.getBaseString(), "G"); + List alleles = vc.getAlleles(); + assertEquals(alleles.size(), 2); + assertTrue(alleles.get(0).isReference()); + assertEquals(alleles.get(0).getBaseString(), "G"); + assertFalse(alleles.get(1).isReference()); + assertEquals(alleles.get(1).getBaseString(), "GA"); + + assertFalse(variants.hasNext()); + + variants.close(); + reader.close(); + } +} diff --git a/test/jalview/ext/jmol/JmolCommandsTest.java b/test/jalview/ext/jmol/JmolCommandsTest.java index 3309adf..e42b54f 100644 --- a/test/jalview/ext/jmol/JmolCommandsTest.java +++ b/test/jalview/ext/jmol/JmolCommandsTest.java @@ -115,7 +115,7 @@ public class JmolCommandsTest String chainACommand = commands[0].commands[0]; // M colour is #82827d == (130, 130, 125) (see strand.html help page) assertTrue(chainACommand - .contains(";select 21:A/1.1;color[130,130,125]")); + .contains("select 21:A/1.1;color[130,130,125]")); // first one // H colour is #60609f == (96, 96, 159) assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]")); // hidden columns are Gray (128, 128, 128) @@ -128,7 +128,7 @@ public class JmolCommandsTest String chainBCommand = commands[1].commands[0]; // M colour is #82827d == (130, 130, 125) assertTrue(chainBCommand - .contains(";select 21:B/2.1;color[130,130,125]")); + .contains("select 21:B/2.1;color[130,130,125]")); // V colour is #ffff00 == (255, 255, 0) assertTrue(chainBCommand .contains(";select 22:B/2.1;color[255,255,0]")); diff --git a/test/jalview/ext/jmol/JmolViewerTest.java b/test/jalview/ext/jmol/JmolViewerTest.java index 792f7ad..e451ed2 100644 --- a/test/jalview/ext/jmol/JmolViewerTest.java +++ b/test/jalview/ext/jmol/JmolViewerTest.java @@ -20,11 +20,15 @@ */ package jalview.ext.jmol; +import static org.junit.Assert.assertNotNull; +import static org.testng.Assert.assertEquals; import static org.testng.AssertJUnit.assertTrue; import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; import jalview.bin.Jalview; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.JvOptionPane; @@ -32,6 +36,10 @@ import jalview.gui.Preferences; import jalview.gui.StructureViewer; import jalview.gui.StructureViewer.ViewerType; import jalview.io.DataSourceType; +import jalview.io.FileFormat; +import jalview.io.FileLoader; + +import java.lang.reflect.InvocationTargetException; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -54,8 +62,10 @@ public class JmolViewerTest @BeforeClass(alwaysRun = true) public static void setUpBeforeClass() throws Exception { - Jalview.main(new String[] { "-noquestionnaire", "-nonews", "-props", - "test/jalview/ext/rbvi/chimera/testProps.jvprops" }); + Jalview.main( + new String[] + { "-noquestionnaire", "-nonews", "-props", + "test/jalview/ext/rbvi/chimera/testProps.jvprops" }); } /** @@ -70,10 +80,11 @@ public class JmolViewerTest @Test(groups = { "Functional" }) public void testSingleSeqViewJMol() { - Cache.setProperty(Preferences.STRUCTURE_DISPLAY, ViewerType.JMOL.name()); + Cache.setProperty(Preferences.STRUCTURE_DISPLAY, + ViewerType.JMOL.name()); String inFile = "examples/1gaq.txt"; - AlignFrame af = new jalview.io.FileLoader().LoadFileWaitTillLoaded( - inFile, DataSourceType.FILE); + AlignFrame af = new jalview.io.FileLoader() + .LoadFileWaitTillLoaded(inFile, DataSourceType.FILE); assertTrue("Didn't read input file " + inFile, af != null); for (SequenceI sq : af.getViewport().getAlignment().getSequences()) { @@ -87,13 +98,13 @@ public class JmolViewerTest { for (int q = 0; q < dsq.getAllPDBEntries().size(); q++) { - final StructureViewer structureViewer = new StructureViewer(af - .getViewport().getStructureSelectionManager()); + final StructureViewer structureViewer = new StructureViewer( + af.getViewport().getStructureSelectionManager()); structureViewer.setViewerType(ViewerType.JMOL); JalviewStructureDisplayI jmolViewer = structureViewer .viewStructures(dsq.getAllPDBEntries().elementAt(q), - new SequenceI[] { sq }, af.getCurrentView() - .getAlignPanel()); + new SequenceI[] + { sq }, af.getCurrentView().getAlignPanel()); /* * Wait for viewer load thread to complete */ @@ -116,5 +127,86 @@ public class JmolViewerTest } } + @Test(groups = { "Functional" }) + public void testAddStrToSingleSeqViewJMol() + throws InvocationTargetException, InterruptedException + { + Cache.setProperty(Preferences.STRUCTURE_DISPLAY, + ViewerType.JMOL.name()); + String inFile = "examples/1gaq.txt"; + AlignFrame af = new jalview.io.FileLoader(true) + .LoadFileWaitTillLoaded(inFile, DataSourceType.FILE); + assertTrue("Didn't read input file " + inFile, af != null); + // show a structure for 4th Sequence + SequenceI sq1 = af.getViewport().getAlignment().getSequences().get(0); + final StructureViewer structureViewer = new StructureViewer( + af.getViewport().getStructureSelectionManager()); + structureViewer.setViewerType(ViewerType.JMOL); + JalviewStructureDisplayI jmolViewer = structureViewer.viewStructures( + sq1.getDatasetSequence().getAllPDBEntries().elementAt(0), + new SequenceI[] + { sq1 }, af.getCurrentView().getAlignPanel()); + /* + * Wait for viewer load thread to complete + */ + try + { + while (!jmolViewer.getBinding().isFinishedInit()) + { + Thread.sleep(500); + } + } catch (InterruptedException e) + { + } + + assertTrue(jmolViewer.isVisible()); + + // add another pdb file and add it to view + final String _inFile = "examples/3W5V.pdb"; + inFile = _inFile; + FileLoader fl = new FileLoader(); + fl.LoadFile(af.getCurrentView(), _inFile, DataSourceType.FILE, + FileFormat.PDB); + try + { + int time = 0; + do + { + Thread.sleep(50); // hope we can avoid race condition + + } while (++time < 30 + && af.getViewport().getAlignment().getHeight() == 3); + } catch (Exception q) + { + } + ; + assertTrue("Didn't paste additional structure" + inFile, + af.getViewport().getAlignment().getHeight() > 3); + SequenceI sq2 = af.getViewport().getAlignment().getSequenceAt(3); + PDBEntry pdbe = sq2.getDatasetSequence().getAllPDBEntries().get(0); + assertTrue(pdbe.getFile().contains(inFile)); + structureViewer.viewStructures(pdbe, new SequenceI[] { sq2 }, + af.alignPanel); + /* + * Wait for viewer load thread to complete + */ + try + { + while (structureViewer.isBusy()) + { + Thread.sleep(500); + } + } catch (InterruptedException e) + { + } + assertEquals(jmolViewer.getBinding().getPdbCount(), 2); + String mouseOverTest = "[GLY]293:A.CA/2.1 #2164"; + ((JalviewJmolBinding) jmolViewer.getBinding()).mouseOverStructure(2164, + mouseOverTest); + SearchResultsI highlight = af.alignPanel.getSeqPanel() + .getLastSearchResults(); + assertNotNull("Didn't find highlight from second structure mouseover", + highlight.getResults(sq2, sq2.getStart(), sq2.getEnd())); + } } diff --git a/test/jalview/ext/rbvi/chimera/ChimeraConnect.java b/test/jalview/ext/rbvi/chimera/ChimeraConnect.java index 4d904cf..99394dc 100644 --- a/test/jalview/ext/rbvi/chimera/ChimeraConnect.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraConnect.java @@ -41,7 +41,7 @@ public class ChimeraConnect JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); } - @Test(groups = { "Functional" }) + @Test(groups = { "External" }) public void testLaunchAndExit() { final StructureManager structureManager = new StructureManager(true); diff --git a/test/jalview/ext/so/SequenceOntologyTest.java b/test/jalview/ext/so/SequenceOntologyTest.java index b76a295..31e1887 100644 --- a/test/jalview/ext/so/SequenceOntologyTest.java +++ b/test/jalview/ext/so/SequenceOntologyTest.java @@ -107,4 +107,29 @@ public class SequenceOntologyTest assertFalse(so.isA("CDS_region", "CDS"));// part_of assertFalse(so.isA("polypeptide", "CDS")); // derives_from } + + @Test(groups = "Functional") + public void testIsSequenceVariant() + { + assertFalse(so.isA("CDS", "sequence_variant")); + assertTrue(so.isA("sequence_variant", "sequence_variant")); + + /* + * these should all be sub-types of sequence_variant + */ + assertTrue(so.isA("structural_variant", "sequence_variant")); + assertTrue(so.isA("feature_variant", "sequence_variant")); + assertTrue(so.isA("gene_variant", "sequence_variant")); + assertTrue(so.isA("transcript_variant", "sequence_variant")); + assertTrue(so.isA("NMD_transcript_variant", "sequence_variant")); + assertTrue(so.isA("missense_variant", "sequence_variant")); + assertTrue(so.isA("synonymous_variant", "sequence_variant")); + assertTrue(so.isA("frameshift_variant", "sequence_variant")); + assertTrue(so.isA("5_prime_UTR_variant", "sequence_variant")); + assertTrue(so.isA("3_prime_UTR_variant", "sequence_variant")); + assertTrue(so.isA("stop_gained", "sequence_variant")); + assertTrue(so.isA("stop_lost", "sequence_variant")); + assertTrue(so.isA("inframe_deletion", "sequence_variant")); + assertTrue(so.isA("inframe_insertion", "sequence_variant")); + } } diff --git a/test/jalview/gui/AlignFrameTest.java b/test/jalview/gui/AlignFrameTest.java index af9c045..b0aaab9 100644 --- a/test/jalview/gui/AlignFrameTest.java +++ b/test/jalview/gui/AlignFrameTest.java @@ -26,10 +26,12 @@ import static org.testng.Assert.assertNotSame; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; +import jalview.api.FeatureColourI; import jalview.bin.Cache; import jalview.bin.Jalview; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; @@ -39,6 +41,7 @@ import jalview.io.FileLoader; import jalview.io.Jalview2xmlTests; import jalview.renderer.ResidueShaderI; import jalview.schemes.BuriedColourScheme; +import jalview.schemes.FeatureColour; import jalview.schemes.HelixColourScheme; import jalview.schemes.JalviewColourScheme; import jalview.schemes.StrandColourScheme; @@ -46,7 +49,7 @@ import jalview.schemes.TurnColourScheme; import jalview.util.MessageManager; import java.awt.Color; -import java.util.List; +import java.util.Iterator; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; @@ -69,54 +72,81 @@ public class AlignFrameTest { SequenceI seq1 = new Sequence("Seq1", "ABCDEFGHIJ"); SequenceI seq2 = new Sequence("Seq2", "ABCDEFGHIJ"); - seq1.addSequenceFeature(new SequenceFeature("Metal", "", 1, 5, - Float.NaN, null)); - seq2.addSequenceFeature(new SequenceFeature("Metal", "", 6, 10, - Float.NaN, null)); + seq1.addSequenceFeature(new SequenceFeature("Metal", "", 1, 5, 0f, null)); + seq2.addSequenceFeature(new SequenceFeature("Metal", "", 6, 10, 10f, + null)); seq1.addSequenceFeature(new SequenceFeature("Turn", "", 2, 4, Float.NaN, null)); seq2.addSequenceFeature(new SequenceFeature("Turn", "", 7, 9, Float.NaN, null)); AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 }); - AlignFrame alignFrame = new AlignFrame(al, al.getWidth(), al.getHeight()); + AlignFrame alignFrame = new AlignFrame(al, al.getWidth(), + al.getHeight()); + + /* + * make all features visible (select feature columns checks visibility) + */ + alignFrame.getFeatureRenderer().findAllFeatures(true); /* * hiding a feature not present does nothing */ assertFalse(alignFrame.hideFeatureColumns("exon", true)); assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty()); - assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns() - .getHiddenColumnsCopy() - .isEmpty()); + + assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns() + .getNumberOfRegions(), 0); + assertFalse(alignFrame.hideFeatureColumns("exon", false)); assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty()); - assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns() - .getHiddenColumnsCopy() - .isEmpty()); + + assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns() + .getNumberOfRegions(), 0); /* * hiding a feature in all columns does nothing */ assertFalse(alignFrame.hideFeatureColumns("Metal", true)); assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty()); - List hidden = alignFrame.getViewport().getAlignment() - .getHiddenColumns() - .getHiddenColumnsCopy(); - assertTrue(hidden.isEmpty()); + + assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns() + .getNumberOfRegions(), 0); + + + /* + * threshold Metal to hide features where score < 5 + * seq1 feature in columns 1-5 is hidden + * seq2 feature in columns 6-10 is shown + */ + FeatureColourI fc = new FeatureColour(Color.red, Color.blue, 0f, 10f); + fc.setAboveThreshold(true); + fc.setThreshold(5f); + alignFrame.getFeatureRenderer().setColour("Metal", fc); + assertTrue(alignFrame.hideFeatureColumns("Metal", true)); + HiddenColumns hidden = alignFrame.getViewport().getAlignment().getHiddenColumns(); + assertEquals(hidden.getNumberOfRegions(), 1); + Iterator regions = hidden.iterator(); + int[] next = regions.next(); + assertEquals(next[0], 5); + assertEquals(next[1], 9); /* * hide a feature present in some columns * sequence positions [2-4], [7-9] are column positions * [1-3], [6-8] base zero */ + alignFrame.getViewport().showAllHiddenColumns(); assertTrue(alignFrame.hideFeatureColumns("Turn", true)); - hidden = alignFrame.getViewport().getAlignment().getHiddenColumns() - .getHiddenColumnsCopy(); - assertEquals(hidden.size(), 2); - assertEquals(hidden.get(0)[0], 1); - assertEquals(hidden.get(0)[1], 3); - assertEquals(hidden.get(1)[0], 6); - assertEquals(hidden.get(1)[1], 8); + regions = alignFrame.getViewport().getAlignment() + .getHiddenColumns().iterator(); + assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns() + .getNumberOfRegions(), 2); + next = regions.next(); + assertEquals(next[0], 1); + assertEquals(next[1], 3); + next = regions.next(); + assertEquals(next[0], 6); + assertEquals(next[1], 8); } @BeforeClass(alwaysRun = true) diff --git a/test/jalview/gui/AlignViewportTest.java b/test/jalview/gui/AlignViewportTest.java index 5ed0cac..7801250 100644 --- a/test/jalview/gui/AlignViewportTest.java +++ b/test/jalview/gui/AlignViewportTest.java @@ -277,7 +277,7 @@ public class AlignViewportTest * Test for JAL-1306 - conservation thread should run even when only Quality * (and not Conservation) is enabled in Preferences */ - @Test(groups = { "Functional" }) + @Test(groups = { "Functional" }, timeOut=2000) public void testUpdateConservation_qualityOnly() { Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", @@ -292,7 +292,24 @@ public class AlignViewportTest Boolean.FALSE.toString()); AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( "examples/uniref50.fa", DataSourceType.FILE); - AlignmentAnnotation[] anns = af.viewport.getAlignment() + + /* + * wait for Conservation thread to complete + */ + AlignViewport viewport = af.getViewport(); + synchronized (this) + { + while (viewport.getAlignmentConservationAnnotation() != null) + { + try + { + wait(50); + } catch (InterruptedException e) + { + } + } + } + AlignmentAnnotation[] anns = viewport.getAlignment() .getAlignmentAnnotation(); assertNotNull("No annotations found", anns); assertEquals("More than one annotation found", 1, anns.length); diff --git a/test/jalview/gui/AlignmentPanelTest.java b/test/jalview/gui/AlignmentPanelTest.java index 2819dbf..e84b87a 100644 --- a/test/jalview/gui/AlignmentPanelTest.java +++ b/test/jalview/gui/AlignmentPanelTest.java @@ -222,7 +222,7 @@ public class AlignmentPanelTest /** * Test that update layout reverts to original (unwrapped) values for endRes - * and endSeq when switching from wrapped to unwrapped mode (JAL-2739) + * when switching from wrapped back to unwrapped mode (JAL-2739) */ @Test(groups = "Functional") public void TestUpdateLayout_endRes() @@ -235,14 +235,14 @@ public class AlignmentPanelTest af.alignPanel.getAlignViewport().setWrapAlignment(true); af.alignPanel.updateLayout(); - // endRes changes + // endRes has changed assertNotEquals(ranges.getEndRes(), endres); // unwrap af.alignPanel.getAlignViewport().setWrapAlignment(false); af.alignPanel.updateLayout(); - // endRes and endSeq back to original values + // endRes back to original value assertEquals(ranges.getEndRes(), endres); } diff --git a/test/jalview/gui/AnnotationColumnChooserTest.java b/test/jalview/gui/AnnotationColumnChooserTest.java index 06478d5..912cd27 100644 --- a/test/jalview/gui/AnnotationColumnChooserTest.java +++ b/test/jalview/gui/AnnotationColumnChooserTest.java @@ -35,7 +35,7 @@ import jalview.io.FileFormat; import jalview.io.FormatAdapter; import java.io.IOException; -import java.util.List; +import java.util.Iterator; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; @@ -141,18 +141,21 @@ public class AnnotationColumnChooserTest HiddenColumns currentHidden = af.getViewport().getAlignment() .getHiddenColumns(); - List regions = currentHidden.getHiddenColumnsCopy(); - assertEquals(regions.get(0)[0], 0); - assertEquals(regions.get(0)[1], 3); - assertEquals(regions.get(1)[0], 22); - assertEquals(regions.get(1)[1], 25); + Iterator regions = currentHidden.iterator(); + int[] next = regions.next(); + assertEquals(0, next[0]); + assertEquals(3, next[1]); + next = regions.next(); + assertEquals(22, next[0]); + assertEquals(25, next[1]); // now reset hidden columns acc.reset(); currentHidden = af.getViewport().getAlignment().getHiddenColumns(); - regions = currentHidden.getHiddenColumnsCopy(); - assertEquals(regions.get(0)[0], 10); - assertEquals(regions.get(0)[1], 20); + regions = currentHidden.iterator(); + next = regions.next(); + assertEquals(10, next[0]); + assertEquals(20, next[1]); // check works with empty hidden columns as old columns oldhidden = new HiddenColumns(); @@ -169,8 +172,9 @@ public class AnnotationColumnChooserTest acc.reset(); currentHidden = af.getViewport().getAlignment().getHiddenColumns(); - regions = currentHidden.getHiddenColumnsCopy(); - assertEquals(regions.get(0)[0], 10); - assertEquals(regions.get(0)[1], 20); + regions = currentHidden.iterator(); + next = regions.next(); + assertEquals(10, next[0]); + assertEquals(20, next[1]); } } diff --git a/test/jalview/gui/FeatureSettingsTest.java b/test/jalview/gui/FeatureSettingsTest.java new file mode 100644 index 0000000..6ddebf8 --- /dev/null +++ b/test/jalview/gui/FeatureSettingsTest.java @@ -0,0 +1,191 @@ +package jalview.gui; + +import static org.testng.Assert.assertEquals; +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.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); + 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); + } +} diff --git a/test/jalview/gui/PopupMenuTest.java b/test/jalview/gui/PopupMenuTest.java index 335240b..6f60588 100644 --- a/test/jalview/gui/PopupMenuTest.java +++ b/test/jalview/gui/PopupMenuTest.java @@ -26,21 +26,31 @@ import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; +import jalview.bin.Cache; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; +import jalview.datamodel.ColumnSelection; import jalview.datamodel.DBRefEntry; import jalview.datamodel.DBRefSource; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.io.DataSourceType; import jalview.io.FileFormat; import jalview.io.FormatAdapter; +import jalview.urls.api.UrlProviderFactoryI; +import jalview.urls.desktop.DesktopUrlProviderFactory; import jalview.util.MessageManager; +import jalview.util.UrlConstants; import java.awt.Component; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import javax.swing.JMenu; @@ -80,6 +90,25 @@ public class PopupMenuTest @BeforeMethod(alwaysRun = true) public void setUp() throws IOException { + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + String inMenuString = ("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$" + + SEQUENCE_ID + + "$" + + "|" + + "UNIPROT | http://www.uniprot.org/uniprot/$" + DB_ACCESSION + "$") + + "|" + + ("INTERPRO | http://www.ebi.ac.uk/interpro/entry/$" + + DB_ACCESSION + "$") + + "|" + + + // Gene3D entry tests for case (in)sensitivity + ("Gene3D | http://gene3d.biochem.ucl.ac.uk/Gene3D/search?sterm=$" + + DB_ACCESSION + "$&mode=protein"); + + UrlProviderFactoryI factory = new DesktopUrlProviderFactory( + UrlConstants.DEFAULT_LABEL, inMenuString, ""); + Preferences.sequenceUrlLinks = factory.createUrlProvider(); + alignment = new FormatAdapter().readFile(TEST_DATA, DataSourceType.PASTE, FileFormat.Fasta); AlignFrame af = new AlignFrame(alignment, 700, 500); @@ -100,7 +129,7 @@ public class PopupMenuTest public void testConfigureReferenceAnnotationsMenu_noSequenceSelected() { JMenuItem menu = new JMenuItem(); - List seqs = new ArrayList(); + List seqs = new ArrayList<>(); testee.configureReferenceAnnotationsMenu(menu, seqs); assertFalse(menu.isEnabled()); // now try null list @@ -469,8 +498,8 @@ public class PopupMenuTest List seqs = parentPanel.getAlignment().getSequences(); // create list of links and list of DBRefs - List links = new ArrayList(); - List refs = new ArrayList(); + List links = new ArrayList<>(); + List refs = new ArrayList<>(); // links as might be added into Preferences | Connections dialog links.add("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$" @@ -495,17 +524,19 @@ public class PopupMenuTest // add all the dbrefs to the sequences: Uniprot 1 each, Interpro all 3 to // seq0, Gene3D to seq1 - seqs.get(0).addDBRef(refs.get(0)); + SequenceI seq = seqs.get(0); + seq.addDBRef(refs.get(0)); - seqs.get(0).addDBRef(refs.get(1)); - seqs.get(0).addDBRef(refs.get(2)); - seqs.get(0).addDBRef(refs.get(3)); + seq.addDBRef(refs.get(1)); + seq.addDBRef(refs.get(2)); + seq.addDBRef(refs.get(3)); seqs.get(1).addDBRef(refs.get(4)); seqs.get(1).addDBRef(refs.get(5)); // get the Popup Menu for first sequence - testee = new PopupMenu(parentPanel, (Sequence) seqs.get(0), links); + List noFeatures = Collections. emptyList(); + testee = new PopupMenu(parentPanel, seq, noFeatures); Component[] seqItems = testee.sequenceMenu.getMenuComponents(); JMenu linkMenu = (JMenu) seqItems[6]; Component[] linkItems = linkMenu.getMenuComponents(); @@ -519,15 +550,18 @@ public class PopupMenuTest // sequence id for each link should match corresponding DB accession id for (int i = 1; i < 4; i++) { - assertEquals(refs.get(i - 1).getSource(), ((JMenuItem) linkItems[i]) + String msg = seq.getName() + " link[" + i + "]"; + assertEquals(msg, refs.get(i - 1).getSource(), + ((JMenuItem) linkItems[i]) .getText().split("\\|")[0]); - assertEquals(refs.get(i - 1).getAccessionId(), + assertEquals(msg, refs.get(i - 1).getAccessionId(), ((JMenuItem) linkItems[i]) .getText().split("\\|")[1]); } // get the Popup Menu for second sequence - testee = new PopupMenu(parentPanel, (Sequence) seqs.get(1), links); + seq = seqs.get(1); + testee = new PopupMenu(parentPanel, seq, noFeatures); seqItems = testee.sequenceMenu.getMenuComponents(); linkMenu = (JMenu) seqItems[6]; linkItems = linkMenu.getMenuComponents(); @@ -541,22 +575,136 @@ public class PopupMenuTest // sequence id for each link should match corresponding DB accession id for (int i = 1; i < 3; i++) { - assertEquals(refs.get(i + 3).getSource(), ((JMenuItem) linkItems[i]) + String msg = seq.getName() + " link[" + i + "]"; + assertEquals(msg, refs.get(i + 3).getSource(), + ((JMenuItem) linkItems[i]) .getText().split("\\|")[0].toUpperCase()); - assertEquals(refs.get(i + 3).getAccessionId(), + assertEquals(msg, refs.get(i + 3).getAccessionId(), ((JMenuItem) linkItems[i]).getText().split("\\|")[1]); } // if there are no valid links the Links submenu is disabled - List nomatchlinks = new ArrayList(); + List nomatchlinks = new ArrayList<>(); nomatchlinks.add("NOMATCH | http://www.uniprot.org/uniprot/$" + DB_ACCESSION + "$"); - testee = new PopupMenu(parentPanel, (Sequence) seqs.get(0), - nomatchlinks); + testee = new PopupMenu(parentPanel, seq, noFeatures); seqItems = testee.sequenceMenu.getMenuComponents(); linkMenu = (JMenu) seqItems[6]; assertFalse(linkMenu.isEnabled()); } + + /** + * Test for adding feature links + */ + @Test(groups = { "Functional" }) + public void testHideInsertions() + { + // get sequences from the alignment + List seqs = parentPanel.getAlignment().getSequences(); + + // add our own seqs to avoid problems with changes to existing sequences + // (gap at end of sequences varies depending on how tests are run!) + Sequence seqGap1 = new Sequence("GappySeq", + "AAAA----AA-AAAAAAA---AAA-----------AAAAAAAAAA--"); + seqGap1.createDatasetSequence(); + seqs.add(seqGap1); + Sequence seqGap2 = new Sequence("LessGappySeq", + "AAAAAA-AAAAA---AAA--AAAAA--AAAAAAA-AAAAAA"); + seqGap2.createDatasetSequence(); + seqs.add(seqGap2); + Sequence seqGap3 = new Sequence("AnotherGapSeq", + "AAAAAA-AAAAAA--AAAAAA-AAAAAAAAAAA---AAAAAAAA"); + seqGap3.createDatasetSequence(); + seqs.add(seqGap3); + Sequence seqGap4 = new Sequence("NoGaps", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + seqGap4.createDatasetSequence(); + seqs.add(seqGap4); + + ColumnSelection sel = new ColumnSelection(); + parentPanel.av.getAlignment().getHiddenColumns() + .revealAllHiddenColumns(sel); + + // get the Popup Menu for 7th sequence - no insertions + testee = new PopupMenu(parentPanel, seqs.get(7), null); + testee.hideInsertions_actionPerformed(null); + + HiddenColumns hidden = parentPanel.av.getAlignment().getHiddenColumns(); + Iterator it = hidden.iterator(); + assertFalse(it.hasNext()); + + // get the Popup Menu for GappySeq - this time we have insertions + testee = new PopupMenu(parentPanel, seqs.get(4), null); + testee.hideInsertions_actionPerformed(null); + hidden = parentPanel.av.getAlignment().getHiddenColumns(); + it = hidden.iterator(); + + assertTrue(it.hasNext()); + int[] region = it.next(); + assertEquals(region[0], 4); + assertEquals(region[1], 7); + + assertTrue(it.hasNext()); + region = it.next(); + assertEquals(region[0], 10); + assertEquals(region[1], 10); + + assertTrue(it.hasNext()); + region = it.next(); + assertEquals(region[0], 18); + assertEquals(region[1], 20); + + assertTrue(it.hasNext()); + region = it.next(); + assertEquals(region[0], 24); + assertEquals(region[1], 34); + + assertTrue(it.hasNext()); + region = it.next(); + assertEquals(region[0], 45); + assertEquals(region[1], 46); + + assertFalse(it.hasNext()); + + sel = new ColumnSelection(); + hidden.revealAllHiddenColumns(sel); + + // make a sequence group and hide insertions within the group + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(8); + sg.setEndRes(42); + sg.addSequence(seqGap2, false); + sg.addSequence(seqGap3, false); + parentPanel.av.setSelectionGroup(sg); + + // hide columns outside and within selection + // only hidden columns outside the collection will be retained (unless also + // gaps in the selection) + hidden.hideColumns(1, 10); + hidden.hideColumns(31, 40); + + // get the Popup Menu for LessGappySeq in the sequence group + testee = new PopupMenu(parentPanel, seqs.get(5), null); + testee.hideInsertions_actionPerformed(null); + hidden = parentPanel.av.getAlignment().getHiddenColumns(); + it = hidden.iterator(); + + assertTrue(it.hasNext()); + region = it.next(); + assertEquals(region[0], 1); + assertEquals(region[1], 7); + + assertTrue(it.hasNext()); + region = it.next(); + assertEquals(region[0], 13); + assertEquals(region[1], 14); + + assertTrue(it.hasNext()); + region = it.next(); + assertEquals(region[0], 34); + assertEquals(region[1], 34); + } + } diff --git a/test/jalview/gui/SeqCanvasTest.java b/test/jalview/gui/SeqCanvasTest.java index a27bc3f..5298680 100644 --- a/test/jalview/gui/SeqCanvasTest.java +++ b/test/jalview/gui/SeqCanvasTest.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.gui; import static org.testng.Assert.assertEquals; @@ -13,8 +33,6 @@ import junit.extensions.PA; import org.testng.annotations.Test; -import sun.swing.SwingUtilities2; - public class SeqCanvasTest { /** @@ -30,17 +48,17 @@ public class SeqCanvasTest AlignmentI al = av.getAlignment(); assertEquals(al.getWidth(), 157); assertEquals(al.getHeight(), 15); + av.getRanges().setStartEndSeq(0, 14); + + SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas; av.setWrapAlignment(true); - av.getRanges().setStartEndSeq(0, 14); av.setFont(new Font("SansSerif", Font.PLAIN, 14), true); int charHeight = av.getCharHeight(); int charWidth = av.getCharWidth(); assertEquals(charHeight, 17); assertEquals(charWidth, 12); - SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas; - /* * first with scales above, left, right */ @@ -48,7 +66,7 @@ public class SeqCanvasTest av.setScaleAboveWrapped(true); av.setScaleLeftWrapped(true); av.setScaleRightWrapped(true); - FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont()); + FontMetrics fm = testee.getFontMetrics(av.getFont()); int labelWidth = fm.stringWidth("000") + charWidth; assertEquals(labelWidth, 39); // 3 x 9 + charWidth @@ -218,7 +236,7 @@ public class SeqCanvasTest av.setScaleAboveWrapped(true); av.setScaleLeftWrapped(true); av.setScaleRightWrapped(true); - FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont()); + FontMetrics fm = testee.getFontMetrics(av.getFont()); int labelWidth = fm.stringWidth("000") + charWidth; assertEquals(labelWidth, 39); // 3 x 9 + charWidth int annotationHeight = testee.getAnnotationHeight(); @@ -280,4 +298,42 @@ public class SeqCanvasTest testee.calculateWrappedGeometry(canvasWidth, canvasHeight); assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); } + + /** + * Test simulates loading an unwrapped alignment, shrinking it vertically so + * not all sequences are visible, then changing to wrapped mode. The ranges + * endSeq should be unchanged, but the vertical repeat height should include + * all sequences. + */ + @Test(groups = "Functional") + public void testCalculateWrappedGeometry_fromScrolled() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewport av = af.getViewport(); + AlignmentI al = av.getAlignment(); + assertEquals(al.getWidth(), 157); + assertEquals(al.getHeight(), 15); + av.getRanges().setStartEndSeq(0, 3); + av.setShowAnnotation(false); + av.setScaleAboveWrapped(true); + + SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas; + + av.setWrapAlignment(true); + av.setFont(new Font("SansSerif", Font.PLAIN, 14), true); + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + assertEquals(charHeight, 17); + assertEquals(charWidth, 12); + + int canvasWidth = 400; + int canvasHeight = 300; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + + assertEquals(av.getRanges().getEndSeq(), 3); // unchanged + int repeatingHeight = (int) PA.getValue(testee, + "wrappedRepeatHeightPx"); + assertEquals(repeatingHeight, charHeight * (2 + al.getHeight())); + } } diff --git a/test/jalview/gui/StructureChooserTest.java b/test/jalview/gui/StructureChooserTest.java index 91fe602..f69e6b5 100644 --- a/test/jalview/gui/StructureChooserTest.java +++ b/test/jalview/gui/StructureChooserTest.java @@ -21,6 +21,7 @@ package jalview.gui; import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; import jalview.datamodel.DBRefEntry; @@ -28,8 +29,10 @@ import jalview.datamodel.DBRefSource; import jalview.datamodel.PDBEntry; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; +import jalview.fts.api.FTSData; import jalview.jbgui.GStructureChooser.FilterOption; +import java.util.Collection; import java.util.Vector; import org.testng.annotations.AfterMethod; @@ -37,6 +40,8 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import junit.extensions.PA; + public class StructureChooserTest { @@ -142,8 +147,10 @@ public class StructureChooserTest SequenceI[] selectedSeqs = new SequenceI[] { seq }; StructureChooser sc = new StructureChooser(selectedSeqs, seq, null); sc.fetchStructuresMetaData(); - assertTrue(sc.getDiscoveredStructuresSet() != null); - assertTrue(sc.getDiscoveredStructuresSet().size() > 0); + Collection ss = (Collection) PA.getValue(sc, + "discoveredStructuresSet"); + assertNotNull(ss); + assertTrue(ss.size() > 0); } diff --git a/test/jalview/io/AnnotatedPDBFileInputTest.java b/test/jalview/io/AnnotatedPDBFileInputTest.java index e14a478..c0038a1 100644 --- a/test/jalview/io/AnnotatedPDBFileInputTest.java +++ b/test/jalview/io/AnnotatedPDBFileInputTest.java @@ -39,6 +39,7 @@ import jalview.structure.StructureImportSettings.StructureParser; import java.io.File; import java.util.List; +import org.junit.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; @@ -101,18 +102,19 @@ public class AnnotatedPDBFileInputTest } } - @Test(groups = { "Functional" }) + @Test(groups = { "Functional" }, enabled = false) public void checkPDBannotationSource() { - + Assert.fail( + "This test is incorrect - does not verify that JmolParser's annotation rows can be recognised as generated by the Jmol parser."); for (SequenceI asq : al.getSequences()) { for (AlignmentAnnotation aa : asq.getAnnotation()) { System.out.println("CalcId: " + aa.getCalcId()); - if (StructureImportSettings.getDefaultPDBFileParser().equals( - StructureParser.JALVIEW_PARSER)) + if (StructureImportSettings.getDefaultPDBFileParser() + .equals(StructureParser.JALVIEW_PARSER)) { assertTrue(MCview.PDBfile.isCalcIdForFile(aa, pdbId)); } diff --git a/test/jalview/io/ClustalFileTest.java b/test/jalview/io/ClustalFileTest.java new file mode 100644 index 0000000..1da2c75 --- /dev/null +++ b/test/jalview/io/ClustalFileTest.java @@ -0,0 +1,67 @@ +package jalview.io; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import jalview.datamodel.SequenceI; + +import java.io.IOException; + +import org.testng.annotations.Test; + +public class ClustalFileTest +{ + @Test(groups="Functional") + public void testParse_withNumbering() throws IOException + { + //@formatter:off + String data = "CLUSTAL\n\n" + + "FER_CAPAA/1-8 -----------------------------------------------------------A\t1\n" + + "FER_CAPAN/1-55 MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA 48\n" + + "FER1_SOLLC/1-55 MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48\n" + + "Q93XJ9_SOLTU/1-55 MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48\n" + + "FER1_PEA/1-60 MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA 53\n\n" + + "FER_CAPAA/1-8 SYKVKLI 8\n" + + "FER_CAPAN/1-55 SYKVKLI 55\n" + + "FER1_SOLLC/1-55 SYKVKLI 55\n" + + "Q93XJ9_SOLTU/1-55 SYKVKLI 55\n" + + "FER1_PEA/1-60 SYKVKLV 60\n" + + " .* .:....*******..** ..........** ********...*:::* ...\n" + + "\t\t.:.::. *\n"; + //@formatter:on + ClustalFile cf = new ClustalFile(data, DataSourceType.PASTE); + cf.parse(); + SequenceI[] seqs = cf.getSeqsAsArray(); + assertEquals(seqs.length, 5); + assertEquals(seqs[0].getName(), "FER_CAPAA"); + assertEquals(seqs[0].getStart(), 1); + assertEquals(seqs[0].getEnd(), 8); + assertTrue(seqs[0].getSequenceAsString().endsWith("ASYKVKLI")); + } + + @Test(groups="Functional") + public void testParse_noNumbering() throws IOException + { + //@formatter:off + String data = "CLUSTAL\n\n" + + "FER_CAPAA/1-8 -----------------------------------------------------------A\n" + + "FER_CAPAN/1-55 MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA\n" + + "FER1_SOLLC/1-55 MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA\n" + + "Q93XJ9_SOLTU/1-55 MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA\n" + + "FER1_PEA/1-60 MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA\n\n" + + "FER_CAPAA/1-8 SYKVKLI\n" + + "FER_CAPAN/1-55 SYKVKLI\n" + + "FER1_SOLLC/1-55 SYKVKLI\n" + + "Q93XJ9_SOLTU/1-55 SYKVKLI\n" + + "FER1_PEA/1-60 SYKVKLV\n"; + //@formatter:on + ClustalFile cf = new ClustalFile(data, DataSourceType.PASTE); + cf.parse(); + SequenceI[] seqs = cf.getSeqsAsArray(); + assertEquals(seqs.length, 5); + assertEquals(seqs[0].getName(), "FER_CAPAA"); + assertEquals(seqs[0].getStart(), 1); + assertEquals(seqs[0].getEnd(), 8); + assertTrue(seqs[0].getSequenceAsString().endsWith("ASYKVKLI")); + } +} diff --git a/test/jalview/io/CrossRef2xmlTests.java b/test/jalview/io/CrossRef2xmlTests.java index 0715857..2b8a62f 100644 --- a/test/jalview/io/CrossRef2xmlTests.java +++ b/test/jalview/io/CrossRef2xmlTests.java @@ -39,9 +39,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; + +import junit.extensions.PA; import org.testng.Assert; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @Test(singleThreaded = true) @@ -56,6 +60,14 @@ public class CrossRef2xmlTests extends Jalview2xmlBase JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); } + @DataProvider(name = "initialAccessions") + static Object[][] getAccessions() + { + return new String[][] { { "UNIPROT", "P00338" }, + { "UNIPROT", "Q8Z9G6" }, + { "ENSEMBLGENOMES", "CAD01290" } }; + } + /** * test store and recovery of all reachable cross refs from all reachable * crossrefs for one or more fetched db refs. Currently, this test has a known @@ -63,8 +75,13 @@ public class CrossRef2xmlTests extends Jalview2xmlBase * * @throws Exception */ - @Test(groups = { "Operational" }, enabled = true) - public void testRetrieveAndShowCrossref() throws Exception + @Test( + groups = + { "Operational" }, + dataProvider = "initialAccessions", + enabled = true) + public void testRetrieveAndShowCrossref(String forSource, + String forAccession) throws Exception { List failedDBRetr = new ArrayList<>(); @@ -90,12 +107,12 @@ public class CrossRef2xmlTests extends Jalview2xmlBase // . codonframes // // - HashMap dbtoviewBit = new HashMap<>(); + Map dbtoviewBit = new HashMap<>(); List keyseq = new ArrayList<>(); - HashMap savedProjects = new HashMap<>(); + Map savedProjects = new HashMap<>(); - for (String[] did : new String[][] { { "UNIPROT", "P00338" } }) - { +// for (String[] did : new String[][] { { "UNIPROT", "P00338" } }) +// { // pass counters - 0 - first pass, 1 means retrieve project rather than // perform action int pass1 = 0, pass2 = 0, pass3 = 0; @@ -105,7 +122,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase // { pass 2 = 0 { pass 3 = 0 } } do { - String first = did[0] + " " + did[1]; + String first = forSource + " " + forAccession;//did[0] + " " + did[1]; AlignFrame af = null; boolean dna; AlignmentI retral; @@ -117,7 +134,8 @@ public class CrossRef2xmlTests extends Jalview2xmlBase // retrieve dbref List afs = jalview.gui.SequenceFetcher.fetchAndShow( - did[0], did[1]); + forSource, forAccession); + // did[0], did[1]); if (afs.size() == 0) { failedDBRetr.add("Didn't retrieve " + first); @@ -186,15 +204,16 @@ public class CrossRef2xmlTests extends Jalview2xmlBase if (pass2 == 0) { // retrieve and show cross-refs in this thread - cra = new CrossRefAction(af, seqs, dna, db); + cra = CrossRefAction.getHandlerFor(seqs, dna, db, af); cra.run(); - if (cra.getXrefViews().size() == 0) + cra_views = (List) PA.getValue(cra, + "xrefViews"); + if (cra_views.size() == 0) { failedXrefMenuItems.add("No crossrefs retrieved for " + first + " -> " + db); continue; } - cra_views = cra.getXrefViews(); assertNucleotide(cra_views.get(0), "Nucleotide panel included proteins for " + first + " -> " + db); @@ -286,16 +305,18 @@ public class CrossRef2xmlTests extends Jalview2xmlBase if (pass3 == 0) { - SequenceI[] xrseqs = avp.getAlignment() .getSequencesArray(); AlignFrame nextaf = Desktop.getAlignFrameFor(avp .getAlignViewport()); - cra = new CrossRefAction(nextaf, xrseqs, avp - .getAlignViewport().isNucleotide(), xrefdb); + cra = CrossRefAction.getHandlerFor(xrseqs, avp + .getAlignViewport().isNucleotide(), xrefdb, + nextaf); cra.run(); - if (cra.getXrefViews().size() == 0) + cra_views2 = (List) PA.getValue( + cra, "xrefViews"); + if (cra_views2.size() == 0) { failedXrefMenuItems .add("No crossrefs retrieved for '" @@ -303,7 +324,6 @@ public class CrossRef2xmlTests extends Jalview2xmlBase + " via '" + nextaf.getTitle() + "'"); continue; } - cra_views2 = cra.getXrefViews(); assertNucleotide(cra_views2.get(0), "Nucleotide panel included proteins for '" + nextxref + "' to " + xrefdb @@ -411,7 +431,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase pass1++; } } while (pass1 < 3); - } + if (failedXrefMenuItems.size() > 0) { for (String s : failedXrefMenuItems) @@ -541,8 +561,8 @@ public class CrossRef2xmlTests extends Jalview2xmlBase * viewpanel needs to be called with a distinct xrefpath to ensure * each one's strings are compared) */ - private void stringify(HashMap dbtoviewBit, - HashMap savedProjects, String xrefpath, + private void stringify(Map dbtoviewBit, + Map savedProjects, String xrefpath, AlignmentViewPanel avp) { if (savedProjects != null) diff --git a/test/jalview/io/FeaturesFileTest.java b/test/jalview/io/FeaturesFileTest.java index 152ab84..32ca841 100644 --- a/test/jalview/io/FeaturesFileTest.java +++ b/test/jalview/io/FeaturesFileTest.java @@ -23,7 +23,9 @@ package jalview.io; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertSame; import static org.testng.AssertJUnit.assertTrue; +import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; import jalview.api.FeatureColourI; import jalview.api.FeatureRenderer; @@ -32,11 +34,17 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.datamodel.features.SequenceFeatures; import jalview.gui.AlignFrame; import jalview.gui.Desktop; import jalview.gui.JvOptionPane; +import jalview.schemes.FeatureColour; import jalview.structure.StructureSelectionManager; +import jalview.util.matcher.Condition; import java.awt.Color; import java.io.File; @@ -44,6 +52,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -467,10 +476,10 @@ public class FeaturesFileTest */ FeatureRenderer fr = af.alignPanel.getFeatureRenderer(); Map visible = fr.getDisplayedFeatureCols(); - List visibleGroups = new ArrayList( + List visibleGroups = new ArrayList<>( Arrays.asList(new String[] {})); String exported = featuresFile.printJalviewFormat( - al.getSequencesArray(), visible, visibleGroups, false); + al.getSequencesArray(), visible, null, visibleGroups, false); String expected = "No Features Visible"; assertEquals(expected, exported); @@ -479,7 +488,7 @@ public class FeaturesFileTest */ visibleGroups.add("uniprot"); exported = featuresFile.printJalviewFormat(al.getSequencesArray(), - visible, visibleGroups, true); + visible, null, visibleGroups, true); expected = "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\t0.0\n" + "desc1\tFER_CAPAN\t-1\t0\t0\tPfam\t1.3\n" + "desc3\tFER1_SOLLC\t-1\t0\t0\tPfam\n" // NaN is not output @@ -493,9 +502,9 @@ public class FeaturesFileTest fr.setVisible("GAMMA-TURN"); visible = fr.getDisplayedFeatureCols(); exported = featuresFile.printJalviewFormat(al.getSequencesArray(), - visible, visibleGroups, false); + visible, null, visibleGroups, false); expected = "METAL\tcc9900\n" - + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n" + + "GAMMA-TURN\tscore|ff0000|00ffff|noValueMin|20.0|95.0|below|66.0\n" + "\nSTARTGROUP\tuniprot\n" + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n" + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n" @@ -508,13 +517,13 @@ public class FeaturesFileTest fr.setVisible("Pfam"); visible = fr.getDisplayedFeatureCols(); exported = featuresFile.printJalviewFormat(al.getSequencesArray(), - visible, visibleGroups, false); + visible, null, visibleGroups, false); /* * features are output within group, ordered by sequence and by type */ expected = "METAL\tcc9900\n" + "Pfam\tff0000\n" - + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n" + + "GAMMA-TURN\tscore|ff0000|00ffff|noValueMin|20.0|95.0|below|66.0\n" + "\nSTARTGROUP\tuniprot\n" + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n" + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n" @@ -539,8 +548,8 @@ public class FeaturesFileTest */ FeaturesFile featuresFile = new FeaturesFile(); FeatureRenderer fr = af.alignPanel.getFeatureRenderer(); - Map visible = new HashMap(); - List visibleGroups = new ArrayList( + Map visible = new HashMap<>(); + List visibleGroups = new ArrayList<>( Arrays.asList(new String[] {})); String exported = featuresFile.printGffFormat(al.getSequencesArray(), visible, visibleGroups, false); @@ -623,4 +632,79 @@ public class FeaturesFileTest + "FER_CAPAN\tUniprot\tPfam\t20\t20\t0.0\t+\t2\tx=y;black=white\n"; assertEquals(expected, exported); } + + /** + * Test for parsing of feature filters as represented in a Jalview features + * file + * + * @throws Exception + */ + @Test(groups = { "Functional" }) + public void testParseFilters() throws Exception + { + Map filters = new HashMap<>(); + String text = "sequence_variant\tCSQ:PolyPhen NotContains 'damaging'\n" + + "missense_variant\t(label contains foobar) and (Score lt 1.3)"; + FeaturesFile featuresFile = new FeaturesFile(text, + DataSourceType.PASTE); + featuresFile.parseFilters(filters); + assertEquals(filters.size(), 2); + + FeatureMatcherSetI fm = filters.get("sequence_variant"); + assertNotNull(fm); + Iterator matchers = fm.getMatchers().iterator(); + FeatureMatcherI matcher = matchers.next(); + assertFalse(matchers.hasNext()); + String[] attributes = matcher.getAttribute(); + assertArrayEquals(attributes, new String[] { "CSQ", "PolyPhen" }); + assertSame(matcher.getMatcher().getCondition(), Condition.NotContains); + assertEquals(matcher.getMatcher().getPattern(), "damaging"); + + fm = filters.get("missense_variant"); + assertNotNull(fm); + matchers = fm.getMatchers().iterator(); + matcher = matchers.next(); + assertTrue(matcher.isByLabel()); + assertSame(matcher.getMatcher().getCondition(), Condition.Contains); + assertEquals(matcher.getMatcher().getPattern(), "foobar"); + matcher = matchers.next(); + assertTrue(matcher.isByScore()); + assertSame(matcher.getMatcher().getCondition(), Condition.LT); + assertEquals(matcher.getMatcher().getPattern(), "1.3"); + assertEquals(matcher.getMatcher().getFloatValue(), 1.3f); + + assertFalse(matchers.hasNext()); + } + + @Test(groups = { "Functional" }) + public void testOutputFeatureFilters() + { + FeaturesFile ff = new FeaturesFile(); + StringBuilder sb = new StringBuilder(); + Map visible = new HashMap<>(); + visible.put("pfam", new FeatureColour(Color.red)); + Map featureFilters = new HashMap<>(); + + // with no filters, nothing is output + ff.outputFeatureFilters(sb, visible, featureFilters); + assertEquals("", sb.toString()); + + // with filter for not visible features only, nothing is output + FeatureMatcherSet filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byLabel(Condition.Present, null)); + featureFilters.put("foobar", filter); + ff.outputFeatureFilters(sb, visible, featureFilters); + assertEquals("", sb.toString()); + + // with filters for visible feature types + FeatureMatcherSet filter2 = new FeatureMatcherSet(); + filter2.and(FeatureMatcher.byAttribute(Condition.Present, null, "CSQ", + "PolyPhen")); + filter2.and(FeatureMatcher.byScore(Condition.LE, "-2.4")); + featureFilters.put("pfam", filter2); + visible.put("foobar", new FeatureColour(Color.blue)); + ff.outputFeatureFilters(sb, visible, featureFilters); + String expected = "\nSTARTFILTERS\nfoobar\tLabel Present\npfam\t(CSQ:PolyPhen Present) AND (Score LE -2.4)\nENDFILTERS\n\n"; + assertEquals(expected, sb.toString()); + } } diff --git a/test/jalview/io/IdentifyFileTest.java b/test/jalview/io/IdentifyFileTest.java index dd4f6ba..5be7968 100644 --- a/test/jalview/io/IdentifyFileTest.java +++ b/test/jalview/io/IdentifyFileTest.java @@ -94,7 +94,7 @@ public class IdentifyFileTest { "examples/plantfdx.fa", FileFormat.Fasta }, { "examples/dna_interleaved.phy", FileFormat.Phylip }, { "examples/2GIS.pdb", FileFormat.PDB }, - { "examples/rf00031_folded.stk", FileFormat.Stockholm }, + { "examples/RF00031_folded.stk", FileFormat.Stockholm }, { "examples/testdata/test.rnaml", FileFormat.Rnaml }, { "examples/testdata/test.aln", FileFormat.Clustal }, { "examples/testdata/test.pfam", FileFormat.Pfam }, diff --git a/test/jalview/io/JSONFileTest.java b/test/jalview/io/JSONFileTest.java index 158c901..5e835bf 100644 --- a/test/jalview/io/JSONFileTest.java +++ b/test/jalview/io/JSONFileTest.java @@ -42,6 +42,7 @@ import jalview.schemes.ResidueColourScheme; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -205,7 +206,7 @@ public class JSONFileTest TEST_SEQ_HEIGHT = expectedSeqs.size(); TEST_GRP_HEIGHT = expectedGrps.size(); TEST_ANOT_HEIGHT = expectedAnnots.size(); - TEST_CS_HEIGHT = expectedColSel.getHiddenColumnsCopy().size(); + TEST_CS_HEIGHT = expectedColSel.getNumberOfRegions(); exportSettings = new AlignExportSettingI() { @@ -325,11 +326,12 @@ public class JSONFileTest { HiddenColumns cs = testJsonFile.getHiddenColumns(); Assert.assertNotNull(cs); - Assert.assertNotNull(cs.getHiddenColumnsCopy()); - List hiddenCols = cs.getHiddenColumnsCopy(); - Assert.assertEquals(hiddenCols.size(), TEST_CS_HEIGHT); - Assert.assertEquals(hiddenCols.get(0), expectedColSel - .getHiddenColumnsCopy().get(0), + + Iterator it = cs.iterator(); + Iterator colselit = expectedColSel.iterator(); + Assert.assertTrue(it.hasNext()); + Assert.assertEquals(cs.getNumberOfRegions(), TEST_CS_HEIGHT); + Assert.assertEquals(it.next(), colselit.next(), "Mismatched hidden columns!"); } diff --git a/test/jalview/io/Jalview2xmlBase.java b/test/jalview/io/Jalview2xmlBase.java index 15e18e3..fbdd782 100644 --- a/test/jalview/io/Jalview2xmlBase.java +++ b/test/jalview/io/Jalview2xmlBase.java @@ -76,7 +76,8 @@ public class Jalview2xmlBase @BeforeTest(alwaysRun = true) public static void clearDesktop() { - if (Desktop.instance != null && Desktop.getAlignFrames() != null) + if (Desktop.instance != null && Desktop.getFrames() != null + && Desktop.getFrames().length > 0) { Desktop.instance.closeAll_actionPerformed(null); } diff --git a/test/jalview/io/Jalview2xmlTests.java b/test/jalview/io/Jalview2xmlTests.java index 6abb7e5..53bb0e7 100644 --- a/test/jalview/io/Jalview2xmlTests.java +++ b/test/jalview/io/Jalview2xmlTests.java @@ -23,11 +23,13 @@ package jalview.io; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; +import jalview.api.FeatureColourI; import jalview.api.ViewStyleI; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; @@ -35,12 +37,17 @@ import jalview.datamodel.HiddenSequences; import jalview.datamodel.PDBEntry; import jalview.datamodel.PDBEntry.Type; import jalview.datamodel.SequenceCollectionI; +import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.gui.AlignFrame; import jalview.gui.AlignViewport; import jalview.gui.AlignmentPanel; import jalview.gui.Desktop; +import jalview.gui.FeatureRenderer; import jalview.gui.Jalview2XML; import jalview.gui.JvOptionPane; import jalview.gui.PopupMenu; @@ -50,13 +57,16 @@ import jalview.schemes.AnnotationColourGradient; import jalview.schemes.BuriedColourScheme; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemeProperty; +import jalview.schemes.FeatureColour; import jalview.schemes.JalviewColourScheme; import jalview.schemes.RNAHelicesColour; import jalview.schemes.StrandColourScheme; import jalview.schemes.TCoffeeColourScheme; import jalview.structure.StructureImportSettings; +import jalview.util.matcher.Condition; import jalview.viewmodel.AlignmentViewport; +import java.awt.Color; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -247,6 +257,31 @@ public class Jalview2xmlTests extends Jalview2xmlBase } + /** + * Test for JAL-2223 - multiple mappings in View Mapping report + * + * @throws Exception + */ + @Test(groups = { "Functional" }) + public void noDuplicatePdbMappingsMade() throws Exception + { + StructureImportSettings.setProcessSecondaryStructure(true); + StructureImportSettings.setVisibleChainAnnotation(true); + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + "examples/exampleFile_2_7.jar", DataSourceType.FILE); + assertNotNull(af, "Didn't read in the example file correctly."); + + // locate Jmol viewer + // count number of PDB mappings the structure selection manager holds - + String pdbFile = af.getCurrentView().getStructureSelectionManager() + .findFileForPDBId("1A70"); + assertEquals( + af.getCurrentView().getStructureSelectionManager() + .getMapping(pdbFile).length, + 2, "Expected only two mappings for 1A70"); + + } + @Test(groups = { "Functional" }) public void viewRefPdbAnnotation() throws Exception { @@ -413,7 +448,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase String afid = af.getViewport().getSequenceSetId(); // remember reference sequence for each panel - Map refseqs = new HashMap(); + Map refseqs = new HashMap<>(); /* * mark sequence 2, 3, 4.. in panels 1, 2, 3... @@ -551,8 +586,8 @@ public class Jalview2xmlTests extends Jalview2xmlBase * remember representative and hidden sequences marked * on each panel */ - Map repSeqs = new HashMap(); - Map> hiddenSeqNames = new HashMap>(); + Map repSeqs = new HashMap<>(); + Map> hiddenSeqNames = new HashMap<>(); /* * mark sequence 2, 3, 4.. in panels 1, 2, 3... @@ -568,7 +603,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase repIndex = Math.max(repIndex, 1); SequenceI repSeq = alignment.getSequenceAt(repIndex); repSeqs.put(ap.getViewName(), repSeq); - List hiddenNames = new ArrayList(); + List hiddenNames = new ArrayList<>(); hiddenSeqNames.put(ap.getViewName(), hiddenNames); /* @@ -841,4 +876,163 @@ public class Jalview2xmlTests extends Jalview2xmlBase assertTrue(rs.conservationApplied()); assertEquals(rs.getConservationInc(), 30); } + + /** + * Test save and reload of feature colour schemes and filter settings + * + * @throws IOException + */ + @Test(groups = { "Functional" }) + public void testSaveLoadFeatureColoursAndFilters() throws IOException + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE); + 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(); + fr.findAllFeatures(true); + + // 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 as Jalview project + */ + File tfile = File.createTempFile("JalviewTest", ".jvp"); + tfile.deleteOnExit(); + String filePath = tfile.getAbsolutePath(); + assertTrue(af.saveAlignment(filePath, FileFormat.Jalview), + "Failed to store as a project."); + + /* + * close current alignment and load the saved project + */ + af.closeMenuItem_actionPerformed(true); + af = null; + af = new FileLoader() + .LoadFileWaitTillLoaded(filePath, DataSourceType.FILE); + assertNotNull(af, "Failed to import new project"); + + /* + * verify restored feature colour schemes and filters + */ + fr = af.getFeatureRenderer(); + 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)"); + } + + 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); + } + + /** + * 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); + } } diff --git a/test/jalview/io/SequenceAnnotationReportTest.java b/test/jalview/io/SequenceAnnotationReportTest.java index 9e61bec..87e35c7 100644 --- a/test/jalview/io/SequenceAnnotationReportTest.java +++ b/test/jalview/io/SequenceAnnotationReportTest.java @@ -23,15 +23,18 @@ package jalview.io; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; +import jalview.api.FeatureColourI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.gui.JvOptionPane; import jalview.io.gff.GffConstants; +import jalview.renderer.seqfeatures.FeatureRenderer; +import jalview.schemes.FeatureColour; +import jalview.viewmodel.seqfeatures.FeatureRendererModel; -import java.util.HashMap; -import java.util.Hashtable; +import java.awt.Color; import java.util.Map; import junit.extensions.PA; @@ -95,8 +98,9 @@ public class SequenceAnnotationReportTest SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f, "group"); - Map minmax = new Hashtable(); - sar.appendFeature(sb, 1, minmax, sf); + FeatureRendererModel fr = new FeatureRenderer(null); + Map minmax = fr.getMinMax(); + sar.appendFeature(sb, 1, fr, sf); /* * map has no entry for this feature type - score is not shown: */ @@ -106,7 +110,7 @@ public class SequenceAnnotationReportTest * map has entry for this feature type - score is shown: */ minmax.put("METAL", new float[][] { { 0f, 1f }, null }); - sar.appendFeature(sb, 1, minmax, sf); + sar.appendFeature(sb, 1, fr, sf); //
            is appended to a buffer > 6 in length assertEquals("METAL 1 3; Fe2-S
            METAL 1 3; Fe2-S Score=1.3", sb.toString()); @@ -116,7 +120,7 @@ public class SequenceAnnotationReportTest */ minmax.put("METAL", new float[][] { { 2f, 2f }, null }); sb.setLength(0); - sar.appendFeature(sb, 1, minmax, sf); + sar.appendFeature(sb, 1, fr, sf); assertEquals("METAL 1 3; Fe2-S", sb.toString()); } @@ -132,8 +136,11 @@ public class SequenceAnnotationReportTest assertEquals("METAL 1 3; Fe2-S", sb.toString()); } + /** + * A specific attribute value is included if it is used to colour the feature + */ @Test(groups = "Functional") - public void testAppendFeature_clinicalSignificance() + public void testAppendFeature_colouredByAttribute() { SequenceAnnotationReport sar = new SequenceAnnotationReport(null); StringBuilder sb = new StringBuilder(); @@ -141,12 +148,35 @@ public class SequenceAnnotationReportTest Float.NaN, "group"); sf.setValue("clinical_significance", "Benign"); - sar.appendFeature(sb, 1, null, sf); - assertEquals("METAL 1 3; Fe2-S; Benign", sb.toString()); + /* + * first with no colour by attribute + */ + FeatureRendererModel fr = new FeatureRenderer(null); + sar.appendFeature(sb, 1, fr, sf); + assertEquals("METAL 1 3; Fe2-S", sb.toString()); + + /* + * then with colour by an attribute the feature lacks + */ + FeatureColourI fc = new FeatureColour(Color.white, Color.black, 5, 10); + fc.setAttributeName("Pfam"); + fr.setColour("METAL", fc); + sb.setLength(0); + sar.appendFeature(sb, 1, fr, sf); + assertEquals("METAL 1 3; Fe2-S", sb.toString()); // no change + + /* + * then with colour by an attribute the feature has + */ + fc.setAttributeName("clinical_significance"); + sb.setLength(0); + sar.appendFeature(sb, 1, fr, sf); + assertEquals("METAL 1 3; Fe2-S; clinical_significance=Benign", + sb.toString()); } @Test(groups = "Functional") - public void testAppendFeature_withScoreStatusClinicalSignificance() + public void testAppendFeature_withScoreStatusAttribute() { SequenceAnnotationReport sar = new SequenceAnnotationReport(null); StringBuilder sb = new StringBuilder(); @@ -154,11 +184,17 @@ public class SequenceAnnotationReportTest "group"); sf.setStatus("Confirmed"); sf.setValue("clinical_significance", "Benign"); - Map minmax = new Hashtable(); + + FeatureRendererModel fr = new FeatureRenderer(null); + Map minmax = fr.getMinMax(); + FeatureColourI fc = new FeatureColour(Color.white, Color.blue, 12, 22); + fc.setAttributeName("clinical_significance"); + fr.setColour("METAL", fc); minmax.put("METAL", new float[][] { { 0f, 1f }, null }); - sar.appendFeature(sb, 1, minmax, sf); + sar.appendFeature(sb, 1, fr, sf); - assertEquals("METAL 1 3; Fe2-S Score=1.3; (Confirmed); Benign", + assertEquals( + "METAL 1 3; Fe2-S Score=1.3; (Confirmed); clinical_significance=Benign", sb.toString()); } @@ -226,7 +262,7 @@ public class SequenceAnnotationReportTest null)); sb.setLength(0); sar.createSequenceAnnotationReport(sb, seq, true, true, null); - String expected = "
            SeqDesc
            Type1 ; Nonpos
            "; + String expected = "
            SeqDesc
            Type1 ; Nonpos Score=1.0
            "; assertEquals(expected, sb.toString()); /* @@ -244,10 +280,13 @@ public class SequenceAnnotationReportTest */ seq.addSequenceFeature(new SequenceFeature("Metal", "Desc", 0, 0, 5f, null)); - Map minmax = new HashMap(); + + FeatureRendererModel fr = new FeatureRenderer(null); + Map minmax = fr.getMinMax(); minmax.put("Metal", new float[][] { null, new float[] { 2f, 5f } }); + sb.setLength(0); - sar.createSequenceAnnotationReport(sb, seq, true, true, minmax); + sar.createSequenceAnnotationReport(sb, seq, true, true, fr); expected = "
            SeqDesc
            Metal ; Desc
            Type1 ; Nonpos
            "; assertEquals(expected, sb.toString()); @@ -260,19 +299,20 @@ public class SequenceAnnotationReportTest sf.setValue("linkonly", Boolean.TRUE); seq.addSequenceFeature(sf); sb.setLength(0); - sar.createSequenceAnnotationReport(sb, seq, true, true, minmax); + sar.createSequenceAnnotationReport(sb, seq, true, true, fr); assertEquals(expected, sb.toString()); // unchanged! /* - * 'clinical_significance' currently being specially included + * 'clinical_significance' attribute only included when + * used for feature colouring */ SequenceFeature sf2 = new SequenceFeature("Variant", "Havana", 0, 0, 5f, null); sf2.setValue(GffConstants.CLINICAL_SIGNIFICANCE, "benign"); seq.addSequenceFeature(sf2); sb.setLength(0); - sar.createSequenceAnnotationReport(sb, seq, true, true, minmax); - expected = "
            SeqDesc
            Metal ; Desc
            Type1 ; Nonpos
            Variant ; Havana; benign
            "; + sar.createSequenceAnnotationReport(sb, seq, true, true, fr); + expected = "
            SeqDesc
            Metal ; Desc
            Type1 ; Nonpos
            Variant ; Havana
            "; assertEquals(expected, sb.toString()); /* @@ -280,18 +320,24 @@ public class SequenceAnnotationReportTest */ seq.addDBRef(new DBRefEntry("PDB", "0", "3iu1")); seq.addDBRef(new DBRefEntry("Uniprot", "1", "P30419")); + // with showDbRefs = false sb.setLength(0); - sar.createSequenceAnnotationReport(sb, seq, false, true, minmax); + sar.createSequenceAnnotationReport(sb, seq, false, true, fr); assertEquals(expected, sb.toString()); // unchanged - // with showDbRefs = true + + // with showDbRefs = true, colour Variant features by clinical_significance sb.setLength(0); - sar.createSequenceAnnotationReport(sb, seq, true, true, minmax); - expected = "
            SeqDesc
            UNIPROT P30419
            PDB 3iu1
            Metal ; Desc
            Type1 ; Nonpos
            Variant ; Havana; benign
            "; + FeatureColourI fc = new FeatureColour(Color.green, Color.pink, 2, 3); + fc.setAttributeName("clinical_significance"); + fr.setColour("Variant", fc); + sar.createSequenceAnnotationReport(sb, seq, true, true, fr); + expected = "
            SeqDesc
            UNIPROT P30419
            PDB 3iu1
            Metal ; Desc
            " + + "Type1 ; Nonpos
            Variant ; Havana; clinical_significance=benign
            "; assertEquals(expected, sb.toString()); // with showNonPositionalFeatures = false sb.setLength(0); - sar.createSequenceAnnotationReport(sb, seq, true, false, minmax); + sar.createSequenceAnnotationReport(sb, seq, true, false, fr); expected = "
            SeqDesc
            UNIPROT P30419
            PDB 3iu1
            "; assertEquals(expected, sb.toString()); diff --git a/test/jalview/io/StockholmFileTest.java b/test/jalview/io/StockholmFileTest.java index 4273e6c..e86c8ad 100644 --- a/test/jalview/io/StockholmFileTest.java +++ b/test/jalview/io/StockholmFileTest.java @@ -38,6 +38,8 @@ import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -54,7 +56,8 @@ public class StockholmFileTest } static String PfamFile = "examples/PF00111_seed.stk", - RfamFile = "examples/RF00031_folded.stk"; + RfamFile = "examples/RF00031_folded.stk", + RnaSSTestFile = "examples/rna_ss_test.stk"; @Test(groups = { "Functional" }) public void pfamFileIO() throws Exception @@ -230,8 +233,8 @@ public class StockholmFileTest // we might want to revise this in future int aa_new_size = (aa_new == null ? 0 : aa_new.length); int aa_original_size = (aa_original == null ? 0 : aa_original.length); - Map orig_groups = new HashMap(); - Map new_groups = new HashMap(); + Map orig_groups = new HashMap<>(); + Map new_groups = new HashMap<>(); if (aa_new != null && aa_original != null) { @@ -623,13 +626,13 @@ public class StockholmFileTest { for (char ch : new char[] { '{', '}', '[', ']', '(', ')', '<', '>' }) { - Assert.assertTrue(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0), - "Didn't recognise " + ch + " as a WUSS bracket"); + Assert.assertTrue(StockholmFile.RNASS_BRACKETS.indexOf(ch) >= 0, + "Didn't recognise '" + ch + "' as a WUSS bracket"); } - for (char ch : new char[] { '@', '!', 'V', 'Q', '*', ' ', '-', '.' }) + for (char ch : new char[] { '@', '!', '*', ' ', '-', '.' }) { - Assert.assertFalse(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0), - "Shouldn't recognise " + ch + " as a WUSS bracket"); + Assert.assertFalse(StockholmFile.RNASS_BRACKETS.indexOf(ch) >= 0, + "Shouldn't recognise '" + ch + "' as a WUSS bracket"); } } private static void roundTripSSForRNA(String aliFile, String annFile) @@ -654,4 +657,191 @@ public class StockholmFileTest testAlignmentEquivalence(al, newAl, true, true, true); } + + // this is the single sequence alignment and the SS annotations equivalent to + // the ones in file RnaSSTestFile + String aliFileRnaSS = ">Test.sequence/1-14\n" + + "GUACAAAAAAAAAA"; + String annFileRnaSSAlphaChars = "JALVIEW_ANNOTATION\n" + + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n" + + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|(,(|E,E|H,H|B,B|h,h|e,e|b,b|(,(|E,E|),)|e,e|),)|>,>|\t2.0\n" + + "\n" + + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n" + + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;"; + String wrongAnnFileRnaSSAlphaChars = "JALVIEW_ANNOTATION\n" + + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n" + + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|(,(|H,H|E,E|B,B|h,h|e,e|b,b|(,(|E,E|),)|e,e|),)|>,>|\t2.0\n" + + "\n" + + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n" + + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;"; + @Test(groups = { "Functional" }) + public void stockholmFileRnaSSAlphaChars() throws Exception + { + AppletFormatAdapter af = new AppletFormatAdapter(); + AlignmentI al = af.readFile(RnaSSTestFile, DataSourceType.FILE, + jalview.io.FileFormat.Stockholm); + Iterable aai = al.findAnnotations(null, null, + "Secondary Structure"); + AlignmentAnnotation aa = aai.iterator().next(); + Assert.assertTrue(aa.isRNA(), + "'" + RnaSSTestFile + "' not recognised as RNA SS"); + Assert.assertTrue(aa.isValidStruc(), + "'" + RnaSSTestFile + "' not recognised as valid structure"); + Annotation[] as = aa.annotations; + char[] As = new char[as.length]; + for (int i = 0; i < as.length; i++) + { + As[i] = as[i].secondaryStructure; + } + char[] shouldBe = { '<', '(', 'E', 'H', 'B', 'h', 'e', 'b', '(', 'E', + ')', 'e', ')', '>' }; + Assert.assertTrue( + Arrays.equals(As, shouldBe), + "Annotation is " + new String(As) + " but should be " + + new String(shouldBe)); + + // this should result in the same RNA SS Annotations + AlignmentI newAl = new AppletFormatAdapter().readFile( + aliFileRnaSS, + DataSourceType.PASTE, jalview.io.FileFormat.Fasta); + AnnotationFile aaf = new AnnotationFile(); + aaf.readAnnotationFile(newAl, annFileRnaSSAlphaChars, + DataSourceType.PASTE); + + Assert.assertTrue( + testRnaSSAnnotationsEquivalent(al.getAlignmentAnnotation()[0], + newAl.getAlignmentAnnotation()[0]), + "RNA SS Annotations SHOULD be pair-wise equivalent (but apparently aren't): \n" + + "RNA SS A 1:" + al.getAlignmentAnnotation()[0] + "\n" + + "RNA SS A 2:" + newAl.getAlignmentAnnotation()[0]); + + // this should NOT result in the same RNA SS Annotations + newAl = new AppletFormatAdapter().readFile( + aliFileRnaSS, DataSourceType.PASTE, + jalview.io.FileFormat.Fasta); + aaf = new AnnotationFile(); + aaf.readAnnotationFile(newAl, wrongAnnFileRnaSSAlphaChars, + DataSourceType.PASTE); + + boolean mismatch = testRnaSSAnnotationsEquivalent(al.getAlignmentAnnotation()[0], + newAl.getAlignmentAnnotation()[0]); + Assert.assertFalse(mismatch, + "RNA SS Annotations SHOULD NOT be pair-wise equivalent (but apparently are): \n" + + "RNA SS A 1:" + al.getAlignmentAnnotation()[0] + "\n" + + "RNA SS A 2:" + newAl.getAlignmentAnnotation()[0]); + } + + private static boolean testRnaSSAnnotationsEquivalent( + AlignmentAnnotation a1, + AlignmentAnnotation a2) + { + return a1.rnaSecondaryStructureEquivalent(a2); + } + + String annFileRnaSSWithSpaceChars = "JALVIEW_ANNOTATION\n" + + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n" + + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|.,.|H,H| , |B,B|h,h| , |b,b|(,(|E,E|.,.|e,e|),)|>,>|\t2.0\n" + + "\n" + + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n" + + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;"; + String annFileRnaSSWithoutSpaceChars = "JALVIEW_ANNOTATION\n" + + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n" + + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|.,.|H,H|.,.|B,B|h,h|.,.|b,b|(,(|E,E|.,.|e,e|),)|>,>|\t2.0\n" + + "\n" + + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n" + + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;"; + + String wrongAnnFileRnaSSWithoutSpaceChars = "JALVIEW_ANNOTATION\n" + + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n" + + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|.,.|H,H|Z,Z|B,B|h,h|z,z|b,b|(,(|E,E|.,.|e,e|),)|>,>|\t2.0\n" + + "\n" + + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n" + + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;"; + + @Test(groups = { "Functional" }) + public void stockholmFileRnaSSSpaceChars() throws Exception + { + AlignmentI alWithSpaces = new AppletFormatAdapter().readFile( + aliFileRnaSS, DataSourceType.PASTE, + jalview.io.FileFormat.Fasta); + AnnotationFile afWithSpaces = new AnnotationFile(); + afWithSpaces.readAnnotationFile(alWithSpaces, + annFileRnaSSWithSpaceChars, DataSourceType.PASTE); + + Iterable aaiWithSpaces = alWithSpaces + .findAnnotations(null, null, "Secondary Structure"); + AlignmentAnnotation aaWithSpaces = aaiWithSpaces.iterator().next(); + Assert.assertTrue(aaWithSpaces.isRNA(), + "'" + aaWithSpaces + "' not recognised as RNA SS"); + Assert.assertTrue(aaWithSpaces.isValidStruc(), + "'" + aaWithSpaces + "' not recognised as valid structure"); + Annotation[] annWithSpaces = aaWithSpaces.annotations; + char[] As = new char[annWithSpaces.length]; + for (int i = 0; i < annWithSpaces.length; i++) + { + As[i] = annWithSpaces[i].secondaryStructure; + } + // check all spaces and dots are spaces in the internal representation + char[] shouldBe = { '<', ' ', 'H', ' ', 'B', 'h', ' ', 'b', '(', 'E', + ' ', 'e', ')', '>' }; + Assert.assertTrue(Arrays.equals(As, shouldBe), "Annotation is " + + new String(As) + " but should be " + new String(shouldBe)); + + // this should result in the same RNA SS Annotations + AlignmentI alWithoutSpaces = new AppletFormatAdapter().readFile( + aliFileRnaSS, DataSourceType.PASTE, + jalview.io.FileFormat.Fasta); + AnnotationFile afWithoutSpaces = new AnnotationFile(); + afWithoutSpaces.readAnnotationFile(alWithoutSpaces, + annFileRnaSSWithoutSpaceChars, + DataSourceType.PASTE); + + Assert.assertTrue( + testRnaSSAnnotationsEquivalent( + alWithSpaces.getAlignmentAnnotation()[0], + alWithoutSpaces.getAlignmentAnnotation()[0]), + "RNA SS Annotations SHOULD be pair-wise equivalent (but apparently aren't): \n" + + "RNA SS A 1:" + + alWithSpaces.getAlignmentAnnotation()[0] + .getRnaSecondaryStructure() + + "\n" + "RNA SS A 2:" + + alWithoutSpaces.getAlignmentAnnotation()[0] + .getRnaSecondaryStructure()); + + // this should NOT result in the same RNA SS Annotations + AlignmentI wrongAlWithoutSpaces = new AppletFormatAdapter().readFile( + aliFileRnaSS, DataSourceType.PASTE, + jalview.io.FileFormat.Fasta); + AnnotationFile wrongAfWithoutSpaces = new AnnotationFile(); + wrongAfWithoutSpaces.readAnnotationFile(wrongAlWithoutSpaces, + wrongAnnFileRnaSSWithoutSpaceChars, + DataSourceType.PASTE); + + Assert.assertFalse( + testRnaSSAnnotationsEquivalent( + alWithSpaces.getAlignmentAnnotation()[0], + wrongAlWithoutSpaces.getAlignmentAnnotation()[0]), + "RNA SS Annotations SHOULD NOT be pair-wise equivalent (but apparently are): \n" + + "RNA SS A 1:" + + alWithSpaces.getAlignmentAnnotation()[0] + .getRnaSecondaryStructure() + + "\n" + "RNA SS A 2:" + + wrongAlWithoutSpaces.getAlignmentAnnotation()[0] + .getRnaSecondaryStructure()); + + // check no spaces in the output + // TODO: create a better 'save as ' pattern + alWithSpaces.getAlignmentAnnotation()[0].visible = true; + StockholmFile sf = new StockholmFile(alWithSpaces); + + String stockholmFile = sf.print(alWithSpaces.getSequencesArray(), true); + Pattern noSpacesInRnaSSAnnotation = Pattern + .compile("\\n#=GC SS_cons\\s+\\S{14}\\n"); + Matcher m = noSpacesInRnaSSAnnotation.matcher(stockholmFile); + boolean matches = m.find(); + Assert.assertTrue(matches, + "StockholmFile output does not contain expected output (may contain spaces):\n" + + stockholmFile); + + } } diff --git a/test/jalview/io/cache/JvCacheableInputBoxTest.java b/test/jalview/io/cache/JvCacheableInputBoxTest.java index dfd7973..010a4b2 100644 --- a/test/jalview/io/cache/JvCacheableInputBoxTest.java +++ b/test/jalview/io/cache/JvCacheableInputBoxTest.java @@ -55,10 +55,9 @@ public class JvCacheableInputBoxTest cacheBox.updateCache(); try { - // This 1ms delay is essential to prevent the - // assertion below from executing before - // cacheBox.updateCache() finishes updating the cache - Thread.sleep(100); + // This delay is to let + // cacheBox.updateCache() finish updating the cache + Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); diff --git a/test/jalview/io/gff/SequenceOntologyLiteTest.java b/test/jalview/io/gff/SequenceOntologyLiteTest.java new file mode 100644 index 0000000..0766666 --- /dev/null +++ b/test/jalview/io/gff/SequenceOntologyLiteTest.java @@ -0,0 +1,37 @@ +package jalview.io.gff; + +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; + +import org.testng.annotations.Test; + +public class SequenceOntologyLiteTest +{ + @Test(groups = "Functional") + public void testIsA_sequenceVariant() + { + SequenceOntologyI so = new SequenceOntologyLite(); + + assertFalse(so.isA("CDS", "sequence_variant")); + assertTrue(so.isA("sequence_variant", "sequence_variant")); + + /* + * these should all be sub-types of sequence_variant + */ + assertTrue(so.isA("structural_variant", "sequence_variant")); + assertTrue(so.isA("feature_variant", "sequence_variant")); + assertTrue(so.isA("gene_variant", "sequence_variant")); + assertTrue(so.isA("transcript_variant", "sequence_variant")); + assertTrue(so.isA("NMD_transcript_variant", "sequence_variant")); + assertTrue(so.isA("missense_variant", "sequence_variant")); + assertTrue(so.isA("synonymous_variant", "sequence_variant")); + assertTrue(so.isA("frameshift_variant", "sequence_variant")); + assertTrue(so.isA("5_prime_UTR_variant", "sequence_variant")); + assertTrue(so.isA("3_prime_UTR_variant", "sequence_variant")); + assertTrue(so.isA("stop_gained", "sequence_variant")); + assertTrue(so.isA("stop_lost", "sequence_variant")); + assertTrue(so.isA("inframe_deletion", "sequence_variant")); + assertTrue(so.isA("inframe_insertion", "sequence_variant")); + assertTrue(so.isA("splice_region_variant", "sequence_variant")); + } +} diff --git a/test/jalview/io/vcf/VCFLoaderTest.java b/test/jalview/io/vcf/VCFLoaderTest.java new file mode 100644 index 0000000..7e3c0b4 --- /dev/null +++ b/test/jalview/io/vcf/VCFLoaderTest.java @@ -0,0 +1,681 @@ +package jalview.io.vcf; + +import static org.testng.Assert.assertEquals; + +import jalview.bin.Cache; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.DBRefEntry; +import jalview.datamodel.Mapping; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.datamodel.features.SequenceFeatures; +import jalview.gui.AlignFrame; +import jalview.io.DataSourceType; +import jalview.io.FileLoader; +import jalview.io.gff.Gff3Helper; +import jalview.io.gff.SequenceOntologyI; +import jalview.util.MapList; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class VCFLoaderTest +{ + private static final float DELTA = 0.00001f; + + // columns 9717- of gene P30419 from Ensembl (much modified) + private static final String FASTA = "" + + + /* + * forward strand 'gene' and 'transcript' with two exons + */ + ">gene1/1-25 chromosome:GRCh38:17:45051610:45051634:1\n" + + "CAAGCTGGCGGACGAGAGTGTGACA\n" + + ">transcript1/1-18\n--AGCTGGCG----AGAGTGTGAC-\n" + + /* + * reverse strand gene and transcript (reverse complement alleles!) + */ + + ">gene2/1-25 chromosome:GRCh38:17:45051610:45051634:-1\n" + + "TGTCACACTCTCGTCCGCCAGCTTG\n" + + ">transcript2/1-18\n" + "-GTCACACTCT----CGCCAGCT--\n" + + /* + * 'gene' on chromosome 5 with two transcripts + */ + + ">gene3/1-25 chromosome:GRCh38:5:45051610:45051634:1\n" + + "CAAGCTGGCGGACGAGAGTGTGACA\n" + + ">transcript3/1-18\n--AGCTGGCG----AGAGTGTGAC-\n" + + ">transcript4/1-18\n-----TGG-GGACGAGAGTGTGA-A\n"; + + private static final String[] VCF = { "##fileformat=VCFv4.2", + "##INFO=", + "##reference=Homo_sapiens/GRCh38", + "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO", + // A/T,C variants in position 2 of gene sequence (precedes transcript) + // should create 2 variant features with respective scores + "17\t45051611\t.\tA\tT,C\t1666.64\tRF\tAC=15;AF=5.0e-03,4.0e-03", + // SNP G/C in position 4 of gene sequence, position 2 of transcript + // insertion G/GA is transferred to nucleotide but not to peptide + "17\t45051613\t.\tG\tGA,C\t1666.64\tRF\tAC=15;AF=3.0e-03,2.0e-03" }; + + @BeforeClass + public void setUp() + { + /* + * configure to capture all available VCF and VEP (CSQ) fields + */ + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + Cache.setProperty("VCF_FIELDS", ".*"); + Cache.setProperty("VEP_FIELDS", ".*"); + Cache.initLogger(); + } + + @Test(groups = "Functional") + public void testDoLoad() throws IOException + { + AlignmentI al = buildAlignment(); + + File f = makeVcf(); + VCFLoader loader = new VCFLoader(f.getPath()); + + loader.doLoad(al.getSequencesArray(), null); + + /* + * verify variant feature(s) added to gene + * NB alleles at a locus may not be processed, and features added, + * in the order in which they appear in the VCF record as method + * VariantContext.getAlternateAlleles() does not guarantee order + * - order of assertions here matches what we find (is not important) + */ + List geneFeatures = al.getSequenceAt(0) + .getSequenceFeatures(); + SequenceFeatures.sortFeatures(geneFeatures, true); + assertEquals(geneFeatures.size(), 4); + SequenceFeature sf = geneFeatures.get(0); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 2); + assertEquals(sf.getEnd(), 2); + assertEquals(sf.getScore(), 4.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "A,C"); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + sf = geneFeatures.get(1); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 2); + assertEquals(sf.getEnd(), 2); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 5.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "A,T"); + + sf = geneFeatures.get(2); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 4); + assertEquals(sf.getEnd(), 4); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 2.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "G,C"); + + sf = geneFeatures.get(3); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 4); + assertEquals(sf.getEnd(), 4); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 3.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "G,GA"); + + /* + * verify variant feature(s) added to transcript + */ + List transcriptFeatures = al.getSequenceAt(1) + .getSequenceFeatures(); + assertEquals(transcriptFeatures.size(), 2); + sf = transcriptFeatures.get(0); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 2); + assertEquals(sf.getEnd(), 2); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 2.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "G,C"); + sf = transcriptFeatures.get(1); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 2); + assertEquals(sf.getEnd(), 2); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 3.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "G,GA"); + + /* + * verify SNP variant feature(s) computed and added to protein + * first codon AGC varies to ACC giving S/T + */ + DBRefEntry[] dbRefs = al.getSequenceAt(1).getDBRefs(); + SequenceI peptide = null; + for (DBRefEntry dbref : dbRefs) + { + if (dbref.getMap().getMap().getFromRatio() == 3) + { + peptide = dbref.getMap().getTo(); + } + } + List proteinFeatures = peptide.getSequenceFeatures(); + assertEquals(proteinFeatures.size(), 1); + sf = proteinFeatures.get(0); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 1); + assertEquals(sf.getEnd(), 1); + assertEquals(sf.getType(), SequenceOntologyI.NONSYNONYMOUS_VARIANT); + assertEquals(sf.getDescription(), "p.Ser1Thr"); + } + + private File makeVcf() throws IOException + { + File f = File.createTempFile("Test", ".vcf"); + f.deleteOnExit(); + PrintWriter pw = new PrintWriter(f); + for (String vcfLine : VCF) + { + pw.println(vcfLine); + } + pw.close(); + return f; + } + + /** + * Make a simple alignment with one 'gene' and one 'transcript' + * + * @return + */ + private AlignmentI buildAlignment() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(FASTA, + DataSourceType.PASTE); + + /* + * map gene1 sequence to chromosome (normally done when the sequence is fetched + * from Ensembl and transcripts computed) + */ + AlignmentI alignment = af.getViewport().getAlignment(); + SequenceI gene1 = alignment.findName("gene1"); + int[] to = new int[] { 45051610, 45051634 }; + int[] from = new int[] { gene1.getStart(), gene1.getEnd() }; + gene1.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList(from, to, + 1, 1)); + + /* + * map 'transcript1' to chromosome via 'gene1' + * transcript1/1-18 is gene1/3-10,15-24 + * which is chromosome 45051612-45051619,45051624-45051633 + */ + to = new int[] { 45051612, 45051619, 45051624, 45051633 }; + SequenceI transcript1 = alignment.findName("transcript1"); + from = new int[] { transcript1.getStart(), transcript1.getEnd() }; + transcript1.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList( + from, to, + 1, 1)); + + /* + * map gene2 to chromosome reverse strand + */ + SequenceI gene2 = alignment.findName("gene2"); + to = new int[] { 45051634, 45051610 }; + from = new int[] { gene2.getStart(), gene2.getEnd() }; + gene2.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList(from, to, + 1, 1)); + + /* + * map 'transcript2' to chromosome via 'gene2' + * transcript2/1-18 is gene2/2-11,16-23 + * which is chromosome 45051633-45051624,45051619-45051612 + */ + to = new int[] { 45051633, 45051624, 45051619, 45051612 }; + SequenceI transcript2 = alignment.findName("transcript2"); + from = new int[] { transcript2.getStart(), transcript2.getEnd() }; + transcript2.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList( + from, to, + 1, 1)); + + /* + * add a protein product as a DBRef on transcript1 + */ + SequenceI peptide1 = new Sequence("ENSP001", "SWRECD"); + MapList mapList = new MapList(new int[] { 1, 18 }, new int[] { 1, 6 }, + 3, 1); + Mapping map = new Mapping(peptide1, mapList); + DBRefEntry product = new DBRefEntry("", "", "ENSP001", map); + transcript1.addDBRef(product); + + /* + * add a protein product as a DBRef on transcript2 + */ + SequenceI peptide2 = new Sequence("ENSP002", "VTLSPA"); + mapList = new MapList(new int[] { 1, 18 }, new int[] { 1, 6 }, 3, 1); + map = new Mapping(peptide2, mapList); + product = new DBRefEntry("", "", "ENSP002", map); + transcript2.addDBRef(product); + + /* + * map gene3 to chromosome + */ + SequenceI gene3 = alignment.findName("gene3"); + to = new int[] { 45051610, 45051634 }; + from = new int[] { gene3.getStart(), gene3.getEnd() }; + gene3.setGeneLoci("homo_sapiens", "GRCh38", "5", new MapList(from, to, + 1, 1)); + + /* + * map 'transcript3' to chromosome + */ + SequenceI transcript3 = alignment.findName("transcript3"); + to = new int[] { 45051612, 45051619, 45051624, 45051633 }; + from = new int[] { transcript3.getStart(), transcript3.getEnd() }; + transcript3.setGeneLoci("homo_sapiens", "GRCh38", "5", new MapList( + from, to, + 1, 1)); + + /* + * map 'transcript4' to chromosome + */ + SequenceI transcript4 = alignment.findName("transcript4"); + to = new int[] { 45051615, 45051617, 45051619, 45051632, 45051634, + 45051634 }; + from = new int[] { transcript4.getStart(), transcript4.getEnd() }; + transcript4.setGeneLoci("homo_sapiens", "GRCh38", "5", new MapList( + from, to, + 1, 1)); + + /* + * add a protein product as a DBRef on transcript3 + */ + SequenceI peptide3 = new Sequence("ENSP003", "SWRECD"); + mapList = new MapList(new int[] { 1, 18 }, new int[] { 1, 6 }, 3, 1); + map = new Mapping(peptide3, mapList); + product = new DBRefEntry("", "", "ENSP003", map); + transcript3.addDBRef(product); + + return alignment; + } + + /** + * Test with 'gene' and 'transcript' mapped to the reverse strand of the + * chromosome. The VCF variant positions (in forward coordinates) should get + * correctly located on sequence positions. + * + * @throws IOException + */ + @Test(groups = "Functional") + public void testDoLoad_reverseStrand() throws IOException + { + AlignmentI al = buildAlignment(); + + File f = makeVcf(); + + VCFLoader loader = new VCFLoader(f.getPath()); + + loader.doLoad(al.getSequencesArray(), null); + + /* + * verify variant feature(s) added to gene2 + * gene2/1-25 maps to chromosome 45051634- reverse strand + */ + List geneFeatures = al.getSequenceAt(2) + .getSequenceFeatures(); + SequenceFeatures.sortFeatures(geneFeatures, true); + assertEquals(geneFeatures.size(), 4); + + /* + * variant A/T at 45051611 maps to T/A at gene position 24 + */ + SequenceFeature sf = geneFeatures.get(3); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 24); + assertEquals(sf.getEnd(), 24); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 5.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "T,A"); + + /* + * variant A/C at 45051611 maps to T/G at gene position 24 + */ + sf = geneFeatures.get(2); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 24); + assertEquals(sf.getEnd(), 24); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 4.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "T,G"); + + /* + * variant G/C at 45051613 maps to C/G at gene position 22 + */ + sf = geneFeatures.get(1); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 22); + assertEquals(sf.getEnd(), 22); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 2.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "C,G"); + + /* + * insertion G/GA at 45051613 maps to an insertion at + * the preceding position (21) on reverse strand gene + * reference: CAAGC -> GCTTG/21-25 + * genomic variant: CAAGAC (G/GA) + * gene variant: GTCTTG (G/GT at 21) + */ + sf = geneFeatures.get(0); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 21); + assertEquals(sf.getEnd(), 21); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 3.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "G,GT"); + + /* + * verify 2 variant features added to transcript2 + */ + List transcriptFeatures = al.getSequenceAt(3) + .getSequenceFeatures(); + assertEquals(transcriptFeatures.size(), 2); + + /* + * insertion G/GT at position 21 of gene maps to position 16 of transcript + */ + sf = transcriptFeatures.get(0); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 16); + assertEquals(sf.getEnd(), 16); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 3.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "G,GT"); + + /* + * SNP C/G at position 22 of gene maps to position 17 of transcript + */ + sf = transcriptFeatures.get(1); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 17); + assertEquals(sf.getEnd(), 17); + assertEquals(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT); + assertEquals(sf.getScore(), 2.0e-03, DELTA); + assertEquals(sf.getValue(Gff3Helper.ALLELES), "C,G"); + + /* + * verify variant feature(s) computed and added to protein + * last codon GCT varies to GGT giving A/G in the last peptide position + */ + DBRefEntry[] dbRefs = al.getSequenceAt(3).getDBRefs(); + SequenceI peptide = null; + for (DBRefEntry dbref : dbRefs) + { + if (dbref.getMap().getMap().getFromRatio() == 3) + { + peptide = dbref.getMap().getTo(); + } + } + List proteinFeatures = peptide.getSequenceFeatures(); + assertEquals(proteinFeatures.size(), 1); + sf = proteinFeatures.get(0); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 6); + assertEquals(sf.getEnd(), 6); + assertEquals(sf.getType(), SequenceOntologyI.NONSYNONYMOUS_VARIANT); + assertEquals(sf.getDescription(), "p.Ala6Gly"); + } + + /** + * Tests that if VEP consequence (CSQ) data is present in the VCF data, then + * it is added to the variant feature, but restricted where possible to the + * consequences for a specific transcript + * + * @throws IOException + */ + @Test(groups = "Functional") + public void testDoLoad_vepCsq() throws IOException + { + AlignmentI al = buildAlignment(); + + VCFLoader loader = new VCFLoader("test/jalview/io/vcf/testVcf.vcf"); + + /* + * VCF data file with variants at gene3 positions + * 1 C/A + * 5 C/T + * 9 CGT/C (deletion) + * 13 C/G, C/T + * 17 A/AC (insertion), A/G + */ + loader.doLoad(al.getSequencesArray(), null); + + /* + * verify variant feature(s) added to gene3 + */ + List geneFeatures = al.findName("gene3") + .getSequenceFeatures(); + SequenceFeatures.sortFeatures(geneFeatures, true); + assertEquals(geneFeatures.size(), 7); + SequenceFeature sf = geneFeatures.get(0); + assertEquals(sf.getBegin(), 1); + assertEquals(sf.getEnd(), 1); + assertEquals(sf.getScore(), 0.1f, DELTA); + assertEquals(sf.getValue("alleles"), "C,A"); + // gene features include Consequence for all transcripts + Map map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + + sf = geneFeatures.get(1); + assertEquals(sf.getBegin(), 5); + assertEquals(sf.getEnd(), 5); + assertEquals(sf.getScore(), 0.2f, DELTA); + assertEquals(sf.getValue("alleles"), "C,T"); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + + sf = geneFeatures.get(2); + assertEquals(sf.getBegin(), 9); + assertEquals(sf.getEnd(), 11); // deletion over 3 positions + assertEquals(sf.getScore(), 0.3f, DELTA); + assertEquals(sf.getValue("alleles"), "CGG,C"); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + + sf = geneFeatures.get(3); + assertEquals(sf.getBegin(), 13); + assertEquals(sf.getEnd(), 13); + assertEquals(sf.getScore(), 0.5f, DELTA); + assertEquals(sf.getValue("alleles"), "C,T"); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + + sf = geneFeatures.get(4); + assertEquals(sf.getBegin(), 13); + assertEquals(sf.getEnd(), 13); + assertEquals(sf.getScore(), 0.4f, DELTA); + assertEquals(sf.getValue("alleles"), "C,G"); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + + sf = geneFeatures.get(5); + assertEquals(sf.getBegin(), 17); + assertEquals(sf.getEnd(), 17); + assertEquals(sf.getScore(), 0.7f, DELTA); + assertEquals(sf.getValue("alleles"), "A,G"); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + + sf = geneFeatures.get(6); + assertEquals(sf.getBegin(), 17); + assertEquals(sf.getEnd(), 17); // insertion + assertEquals(sf.getScore(), 0.6f, DELTA); + assertEquals(sf.getValue("alleles"), "A,AC"); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + + /* + * verify variant feature(s) added to transcript3 + * at columns 5 (1), 17 (2), positions 3, 11 + * note the deletion at columns 9-11 is not transferred since col 11 + * has no mapping to transcript 3 + */ + List transcriptFeatures = al.findName("transcript3") + .getSequenceFeatures(); + SequenceFeatures.sortFeatures(transcriptFeatures, true); + assertEquals(transcriptFeatures.size(), 3); + sf = transcriptFeatures.get(0); + assertEquals(sf.getBegin(), 3); + assertEquals(sf.getEnd(), 3); + assertEquals(sf.getScore(), 0.2f, DELTA); + assertEquals(sf.getValue("alleles"), "C,T"); + // transcript features only have Consequence for that transcripts + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3"); + + sf = transcriptFeatures.get(1); + assertEquals(sf.getBegin(), 11); + assertEquals(sf.getEnd(), 11); + assertEquals(sf.getScore(), 0.7f, DELTA); + assertEquals(sf.getValue("alleles"), "A,G"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3"); + + sf = transcriptFeatures.get(2); + assertEquals(sf.getBegin(), 11); + assertEquals(sf.getEnd(), 11); + assertEquals(sf.getScore(), 0.6f, DELTA); + assertEquals(sf.getValue("alleles"), "A,AC"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3"); + + /* + * verify variants computed on protein product for transcript3 + * peptide is SWRECD + * codon variants are AGC/AGT position 1 which is synonymous + * and GAG/GGG which is E/G in position 4 + * the insertion variant is not transferred to the peptide + */ + DBRefEntry[] dbRefs = al.findName("transcript3").getDBRefs(); + SequenceI peptide = null; + for (DBRefEntry dbref : dbRefs) + { + if (dbref.getMap().getMap().getFromRatio() == 3) + { + peptide = dbref.getMap().getTo(); + } + } + List proteinFeatures = peptide.getSequenceFeatures(); + SequenceFeatures.sortFeatures(proteinFeatures, true); + assertEquals(proteinFeatures.size(), 2); + sf = proteinFeatures.get(0); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 1); + assertEquals(sf.getEnd(), 1); + assertEquals(sf.getType(), SequenceOntologyI.SYNONYMOUS_VARIANT); + assertEquals(sf.getDescription(), "agC/agT"); + sf = proteinFeatures.get(1); + assertEquals(sf.getFeatureGroup(), "VCF"); + assertEquals(sf.getBegin(), 4); + assertEquals(sf.getEnd(), 4); + assertEquals(sf.getType(), SequenceOntologyI.NONSYNONYMOUS_VARIANT); + assertEquals(sf.getDescription(), "p.Glu4Gly"); + + /* + * verify variant feature(s) added to transcript4 + * at columns 13 (2) and 17 (2), positions 7 and 11 + */ + transcriptFeatures = al.findName("transcript4").getSequenceFeatures(); + SequenceFeatures.sortFeatures(transcriptFeatures, true); + assertEquals(transcriptFeatures.size(), 4); + sf = transcriptFeatures.get(0); + assertEquals(sf.getBegin(), 7); + assertEquals(sf.getEnd(), 7); + assertEquals(sf.getScore(), 0.5f, DELTA); + assertEquals(sf.getValue("alleles"), "C,T"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); + + sf = transcriptFeatures.get(1); + assertEquals(sf.getBegin(), 7); + assertEquals(sf.getEnd(), 7); + assertEquals(sf.getScore(), 0.4f, DELTA); + assertEquals(sf.getValue("alleles"), "C,G"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); + + sf = transcriptFeatures.get(2); + assertEquals(sf.getBegin(), 11); + assertEquals(sf.getEnd(), 11); + assertEquals(sf.getScore(), 0.7f, DELTA); + assertEquals(sf.getValue("alleles"), "A,G"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); + + sf = transcriptFeatures.get(3); + assertEquals(sf.getBegin(), 11); + assertEquals(sf.getEnd(), 11); + assertEquals(sf.getScore(), 0.6f, DELTA); + assertEquals(sf.getValue("alleles"), "A,AC"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); + } + + /** + * A test that demonstrates loading a contig sequence from an indexed sequence + * database which is the reference for a VCF file + * + * @throws IOException + */ + @Test(groups = "Functional") + public void testLoadVCFContig() throws IOException + { + VCFLoader loader = new VCFLoader( + "test/jalview/io/vcf/testVcf2.vcf"); + + SequenceI seq = loader.loadVCFContig("contig123"); + assertEquals(seq.getLength(), 15); + assertEquals(seq.getSequenceAsString(), "AAAAACCCCCGGGGG"); + List features = seq.getSequenceFeatures(); + SequenceFeatures.sortFeatures(features, true); + assertEquals(features.size(), 2); + SequenceFeature sf = features.get(0); + assertEquals(sf.getBegin(), 8); + assertEquals(sf.getEnd(), 8); + assertEquals(sf.getDescription(), "C,A"); + sf = features.get(1); + assertEquals(sf.getBegin(), 12); + assertEquals(sf.getEnd(), 12); + assertEquals(sf.getDescription(), "G,T"); + + seq = loader.loadVCFContig("contig789"); + assertEquals(seq.getLength(), 25); + assertEquals(seq.getSequenceAsString(), "GGGGGTTTTTAAAAACCCCCGGGGG"); + features = seq.getSequenceFeatures(); + SequenceFeatures.sortFeatures(features, true); + assertEquals(features.size(), 2); + sf = features.get(0); + assertEquals(sf.getBegin(), 2); + assertEquals(sf.getEnd(), 2); + assertEquals(sf.getDescription(), "G,T"); + sf = features.get(1); + assertEquals(sf.getBegin(), 21); + assertEquals(sf.getEnd(), 21); + assertEquals(sf.getDescription(), "G,A"); + + seq = loader.loadVCFContig("contig456"); + assertEquals(seq.getLength(), 20); + assertEquals(seq.getSequenceAsString(), "CCCCCGGGGGTTTTTAAAAA"); + features = seq.getSequenceFeatures(); + SequenceFeatures.sortFeatures(features, true); + assertEquals(features.size(), 1); + sf = features.get(0); + assertEquals(sf.getBegin(), 15); + assertEquals(sf.getEnd(), 15); + assertEquals(sf.getDescription(), "T,C"); + } +} \ No newline at end of file diff --git a/test/jalview/io/vcf/contigs.fasta b/test/jalview/io/vcf/contigs.fasta new file mode 100644 index 0000000..ec839b6 --- /dev/null +++ b/test/jalview/io/vcf/contigs.fasta @@ -0,0 +1,6 @@ +>contig123 +AAAAACCCCCGGGGG +>contig456 +CCCCCGGGGGTTTTTAAAAA +>contig789 +GGGGGTTTTTAAAAACCCCCGGGGG diff --git a/test/jalview/io/vcf/contigs.fasta.fai b/test/jalview/io/vcf/contigs.fasta.fai new file mode 100644 index 0000000..e9f5067 --- /dev/null +++ b/test/jalview/io/vcf/contigs.fasta.fai @@ -0,0 +1,3 @@ +contig123 15 11 15 16 +contig456 20 38 20 21 +contig789 25 70 25 26 diff --git a/test/jalview/io/vcf/testVcf.dat b/test/jalview/io/vcf/testVcf.dat new file mode 100644 index 0000000..77e070c --- /dev/null +++ b/test/jalview/io/vcf/testVcf.dat @@ -0,0 +1,13 @@ +##fileformat=VCFv4.2 +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##reference=/Homo_sapiens/GRCh38 +#CHROM POS ID REF ALT QUAL FILTER INFO +5 45051610 . C A 81.96 RF;AC0 AC=1;AF=0.1;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=A|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,A|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051614 . C T 1666.64 RF AC=1;AF=0.2;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051618 . CGG C 41.94 AC0 AC=1;AF=0.3;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=C|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,C|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,CSQ=CGT|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,CGT|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051622 . C G,T 224.23 RF;AC0 AC=1,2;AF=0.4,0.5;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=G|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,G|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051626 . A AC,G 433.35 RF;AC0 AC=3,4;AF=0.6,0.7;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=G|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,G|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,AC|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,AC|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad diff --git a/test/jalview/io/vcf/testVcf.vcf b/test/jalview/io/vcf/testVcf.vcf new file mode 100644 index 0000000..77e070c --- /dev/null +++ b/test/jalview/io/vcf/testVcf.vcf @@ -0,0 +1,13 @@ +##fileformat=VCFv4.2 +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##reference=/Homo_sapiens/GRCh38 +#CHROM POS ID REF ALT QUAL FILTER INFO +5 45051610 . C A 81.96 RF;AC0 AC=1;AF=0.1;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=A|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,A|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051614 . C T 1666.64 RF AC=1;AF=0.2;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051618 . CGG C 41.94 AC0 AC=1;AF=0.3;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=C|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,C|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,CSQ=CGT|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,CGT|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051622 . C G,T 224.23 RF;AC0 AC=1,2;AF=0.4,0.5;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=G|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,G|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad +5 45051626 . A AC,G 433.35 RF;AC0 AC=3,4;AF=0.6,0.7;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=G|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,G|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,AC|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,AC|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad diff --git a/test/jalview/io/vcf/testVcf2.vcf b/test/jalview/io/vcf/testVcf2.vcf new file mode 100644 index 0000000..aa3792a --- /dev/null +++ b/test/jalview/io/vcf/testVcf2.vcf @@ -0,0 +1,13 @@ +##fileformat=VCFv4.2 +##INFO= +##contig= +##contig= +##contig= +##INFO= +##reference=test/jalview/io/vcf/contigs.fasta +#CHROM POS ID REF ALT QUAL FILTER INFO +contig123 8 . C A 81.96 . AC=1;AF=0.1 +contig123 12 . G T 1666.64 . AC=1;AF=0.2 +contig456 15 . T C 41.94 . AC=1;AF=0.3 +contig789 2 . G T 224.23 . AC=1,2;AF=0 +contig789 21 . G A 433.35 . AC=3;AF=0.6 diff --git a/test/jalview/renderer/OverviewRendererTest.java b/test/jalview/renderer/OverviewRendererTest.java new file mode 100644 index 0000000..1d532f7 --- /dev/null +++ b/test/jalview/renderer/OverviewRendererTest.java @@ -0,0 +1,93 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.renderer; + +import static org.testng.Assert.assertEquals; + +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.gui.AlignViewport; +import jalview.renderer.seqfeatures.FeatureRenderer; +import jalview.schemes.FeatureColour; +import jalview.schemes.ZappoColourScheme; +import jalview.viewmodel.AlignmentViewport; +import jalview.viewmodel.OverviewDimensions; +import jalview.viewmodel.OverviewDimensionsShowHidden; +import jalview.viewmodel.ViewportRanges; + +import java.awt.Color; + +import org.testng.annotations.Test; +public class OverviewRendererTest +{ + + @Test + public void testGetColumnColourFromSequence() + { + OverviewResColourFinder cf = new OverviewResColourFinder(false, + Color.PINK, Color.green); // gapColour, hiddenColour + Sequence seq1 = new Sequence("seq1", "PQ-RL-"); + Sequence seq2 = new Sequence("seq2", "FVE"); + AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 }); + AlignmentViewport av = new AlignViewport(al); + OverviewDimensions od = new OverviewDimensionsShowHidden(new ViewportRanges(al), false); + ResidueShaderI rs = new ResidueShader(new ZappoColourScheme()); + FeatureRenderer fr = new FeatureRenderer(av); + OverviewRenderer or = new OverviewRenderer(fr, od, al, rs, cf); + + // P is magenta (see ResidueProperties.zappo) + assertEquals(or.getColumnColourFromSequence(null, seq1, 0), Color.magenta.getRGB()); + // Q is green + assertEquals(or.getColumnColourFromSequence(null, seq1, 1), + Color.green.getRGB()); + // gap is pink (specified in OverviewResColourFinder constructor above) + assertEquals(or.getColumnColourFromSequence(null, seq1, 2), + Color.pink.getRGB()); + // F is orange + assertEquals(or.getColumnColourFromSequence(null, seq2, 0), + Color.orange.getRGB()); + // E is red + assertEquals(or.getColumnColourFromSequence(null, seq2, 2), + Color.red.getRGB()); + // past end of sequence colour as gap (JAL-2929) + assertEquals(or.getColumnColourFromSequence(null, seq2, 3), + Color.pink.getRGB()); + + /* + * now add a feature on seq1 + */ + seq1.addSequenceFeature( + new SequenceFeature("Pfam", "desc", 1, 4, null)); + fr.findAllFeatures(true); + av.setShowSequenceFeatures(true); + fr.setColour("Pfam", new FeatureColour(Color.yellow)); + assertEquals(or.getColumnColourFromSequence(null, seq1, 0), + Color.yellow.getRGB()); + + // don't show sequence features + av.setShowSequenceFeatures(false); + assertEquals(or.getColumnColourFromSequence(null, seq1, 0), + Color.magenta.getRGB()); + } +} diff --git a/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java b/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java index f6dfed6..d8b905e 100644 --- a/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java +++ b/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java @@ -15,6 +15,7 @@ import jalview.gui.FeatureRenderer; import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; +import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import java.awt.Color; import java.util.List; @@ -172,9 +173,9 @@ public class FeatureColourFinderTest * - currently no way other than mimicking reordering of * table in Feature Settings */ - Object[][] data = new Object[2][]; - data[0] = new Object[] { "Metal", red, true }; - data[1] = new Object[] { "Domain", green, true }; + FeatureSettingsBean[] data = new FeatureSettingsBean[2]; + data[0] = new FeatureSettingsBean("Metal", red, null, true); + data[1] = new FeatureSettingsBean("Domain", green, null, true); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); @@ -182,7 +183,7 @@ public class FeatureColourFinderTest /* * ..and turn off display of Metal */ - data[0][2] = false; + data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.green); @@ -216,8 +217,8 @@ public class FeatureColourFinderTest /* * turn off display of Metal - is this the easiest way to do it?? */ - Object[][] data = new Object[1][]; - data[0] = new Object[] { "Metal", red, false }; + FeatureSettingsBean[] data = new FeatureSettingsBean[1]; + data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.blue); @@ -225,7 +226,7 @@ public class FeatureColourFinderTest /* * turn display of Metal back on */ - data[0] = new Object[] { "Metal", red, true }; + data[0] = new FeatureSettingsBean("Metal", red, null, true); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); @@ -399,9 +400,9 @@ public class FeatureColourFinderTest * 1) 0.6 * green(0, 255, 0) + 0.4 * cyan(0, 255, 255) = (0, 255, 102) * 2) 0.6* red(255, 0, 0) + 0.4 * (0, 255, 102) = (153, 102, 41) rounded */ - Object[][] data = new Object[2][]; - data[0] = new Object[] { "Metal", red, true }; - data[1] = new Object[] { "Domain", green, true }; + FeatureSettingsBean[] data = new FeatureSettingsBean[2]; + data[0] = new FeatureSettingsBean("Metal", red, null, true); + data[1] = new FeatureSettingsBean("Domain", green, null, true); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.cyan, seq, 10); assertEquals(c, new Color(153, 102, 41)); @@ -411,7 +412,7 @@ public class FeatureColourFinderTest * Domain (green) above background (pink) * 0.6 * green(0, 255, 0) + 0.4 * pink(255, 175, 175) = (102, 223, 70) */ - data[0][2] = false; + data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.pink, seq, 10); assertEquals(c, new Color(102, 223, 70)); @@ -447,8 +448,8 @@ public class FeatureColourFinderTest /* * turn off display of Metal */ - Object[][] data = new Object[1][]; - data[0] = new Object[] { "Metal", red, false }; + FeatureSettingsBean[] data = new FeatureSettingsBean[1]; + data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); assertTrue(finder.noFeaturesDisplayed()); @@ -503,9 +504,9 @@ public class FeatureColourFinderTest /* * render order is kd above Metal */ - Object[][] data = new Object[2][]; - data[0] = new Object[] { kdFeature, fc, true }; - data[1] = new Object[] { metalFeature, green, true }; + FeatureSettingsBean[] data = new FeatureSettingsBean[2]; + data[0] = new FeatureSettingsBean(kdFeature, fc, null, true); + data[1] = new FeatureSettingsBean(metalFeature, green, null, true); fr.setFeaturePriority(data); av.setShowSequenceFeatures(true); diff --git a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java index d3cddf9..11b129e 100644 --- a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java +++ b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java @@ -1,21 +1,48 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.renderer.seqfeatures; 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.AlignViewportI; 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.gui.AlignFrame; import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; +import jalview.util.matcher.Condition; +import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import java.awt.Color; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -61,9 +88,8 @@ public class FeatureRendererTest seqs.get(2).addSequenceFeature( new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup")); // bug in findAllFeatures - group not checked for a known feature type - seqs.get(2).addSequenceFeature( - new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, - "RfamGroup")); + seqs.get(2).addSequenceFeature(new SequenceFeature("Rfam", "Desc", 5, 9, + Float.NaN, "RfamGroup")); // existing feature type with null group seqs.get(3).addSequenceFeature( new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null)); @@ -116,13 +142,14 @@ public class FeatureRendererTest * change render order (todo: an easier way) * nb here last comes first in the data array */ - Object[][] data = new Object[3][]; + FeatureSettingsBean[] data = new FeatureSettingsBean[3]; FeatureColourI colour = new FeatureColour(Color.RED); - data[0] = new Object[] { "Rfam", colour, true }; - data[1] = new Object[] { "Pfam", colour, false }; - data[2] = new Object[] { "Scop", colour, false }; + data[0] = new FeatureSettingsBean("Rfam", colour, null, true); + data[1] = new FeatureSettingsBean("Pfam", colour, null, false); + data[2] = new FeatureSettingsBean("Scop", colour, null, false); fr.setFeaturePriority(data); - assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam")); + assertEquals(fr.getRenderOrder(), + Arrays.asList("Scop", "Pfam", "Rfam")); assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam")); /* @@ -217,12 +244,13 @@ public class FeatureRendererTest /* * make "Type2" not displayed */ - Object[][] data = new Object[4][]; FeatureColourI colour = new FeatureColour(Color.RED); - data[0] = new Object[] { "Type1", colour, true }; - data[1] = new Object[] { "Type2", colour, false }; - data[2] = new Object[] { "Type3", colour, true }; - data[3] = new Object[] { "Disulphide Bond", colour, true }; + FeatureSettingsBean[] data = new FeatureSettingsBean[4]; + data[0] = new FeatureSettingsBean("Type1", colour, null, true); + data[1] = new FeatureSettingsBean("Type2", colour, null, false); + data[2] = new FeatureSettingsBean("Type3", colour, null, true); + data[3] = new FeatureSettingsBean("Disulphide Bond", colour, null, + true); fr.setFeaturePriority(data); features = fr.findFeaturesAtColumn(seq, 15); @@ -252,6 +280,37 @@ public class FeatureRendererTest features = fr.findFeaturesAtColumn(seq, 5); assertEquals(features.size(), 1); assertTrue(features.contains(sf8)); + + /* + * give "Type3" features a graduated colour scheme + * - first with no threshold + */ + FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f, + 10f); + fr.getFeatureColours().put("Type3", gc); + features = fr.findFeaturesAtColumn(seq, 8); + assertTrue(features.contains(sf4)); + // now with threshold > 2f - feature score of 1f is excluded + gc.setAboveThreshold(true); + gc.setThreshold(2f); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); + + /* + * make "Type3" graduated colour by attribute "AF" + * - first with no attribute held - feature should be excluded + */ + gc.setAttributeName("AF"); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); + // now with the attribute above threshold - should be included + sf4.setValue("AF", "2.4"); + features = fr.findFeaturesAtColumn(seq, 8); + assertTrue(features.contains(sf4)); + // now with the attribute below threshold - should be excluded + sf4.setValue("AF", "1.4"); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); } @Test(groups = "Functional") @@ -264,7 +323,7 @@ public class FeatureRendererTest FeatureRenderer fr = new FeatureRenderer(av); List features = new ArrayList<>(); - fr.filterFeaturesForDisplay(features, null); // empty list, does nothing + fr.filterFeaturesForDisplay(features); // empty list, does nothing SequenceI seq = av.getAlignment().getSequenceAt(0); SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, @@ -297,7 +356,7 @@ public class FeatureRendererTest * filter out duplicate (co-located) features * note: which gets removed is not guaranteed */ - fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue)); + fr.filterFeaturesForDisplay(features); assertEquals(features.size(), 3); assertTrue(features.contains(sf1) || features.contains(sf4)); assertFalse(features.contains(sf1) && features.contains(sf4)); @@ -306,58 +365,174 @@ public class FeatureRendererTest assertTrue(features.contains(sf5)); /* - * hide group 3 - sf3 is removed, sf2 is retained + * hide groups 2 and 3 makes no difference to this method */ + fr.setGroupVisibility("group2", false); fr.setGroupVisibility("group3", false); features = seq.getSequenceFeatures(); - fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue)); + fr.filterFeaturesForDisplay(features); assertEquals(features.size(), 3); assertTrue(features.contains(sf1) || features.contains(sf4)); assertFalse(features.contains(sf1) && features.contains(sf4)); - assertTrue(features.contains(sf2)); - assertFalse(features.contains(sf3)); + assertTrue(features.contains(sf2) || features.contains(sf3)); + assertFalse(features.contains(sf2) && features.contains(sf3)); assertTrue(features.contains(sf5)); /* - * hide group 2, show group 3 - sf2 is removed, sf3 is retained + * no filtering if transparency is applied */ - fr.setGroupVisibility("group2", false); - fr.setGroupVisibility("group3", true); + fr.setTransparency(0.5f); features = seq.getSequenceFeatures(); - fr.filterFeaturesForDisplay(features, null); - assertEquals(features.size(), 3); - assertTrue(features.contains(sf1) || features.contains(sf4)); - assertFalse(features.contains(sf1) && features.contains(sf4)); - assertFalse(features.contains(sf2)); - assertTrue(features.contains(sf3)); - assertTrue(features.contains(sf5)); + fr.filterFeaturesForDisplay(features); + assertEquals(features.size(), 5); + } + + @Test(groups = "Functional") + public void testGetColour() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n", + DataSourceType.PASTE); + AlignViewportI av = af.getViewport(); + FeatureRenderer fr = new FeatureRenderer(av); /* - * no filtering of co-located features with graduated colour scheme - * filterFeaturesForDisplay does _not_ check colour threshold - * sf2 is removed as its group is hidden + * simple colour, feature type and group displayed */ - features = seq.getSequenceFeatures(); - fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black, - Color.white, 0f, 1f)); - assertEquals(features.size(), 4); - assertTrue(features.contains(sf1)); - assertTrue(features.contains(sf3)); - assertTrue(features.contains(sf4)); - assertTrue(features.contains(sf5)); + FeatureColourI fc = new FeatureColour(Color.red); + fr.getFeatureColours().put("Cath", fc); + SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, + "group1"); + assertEquals(fr.getColour(sf1), Color.red); /* - * co-located features with colour by label - * should not get filtered + * hide feature type, then unhide + * - feature type visibility should not affect the result */ - features = seq.getSequenceFeatures(); - FeatureColour fc = new FeatureColour(Color.black); - fc.setColourByLabel(true); - fr.filterFeaturesForDisplay(features, fc); - assertEquals(features.size(), 4); - assertTrue(features.contains(sf1)); - assertTrue(features.contains(sf3)); - assertTrue(features.contains(sf4)); - assertTrue(features.contains(sf5)); + FeatureSettingsBean[] data = new FeatureSettingsBean[1]; + data[0] = new FeatureSettingsBean("Cath", fc, null, false); + fr.setFeaturePriority(data); + assertEquals(fr.getColour(sf1), Color.red); + data[0] = new FeatureSettingsBean("Cath", fc, null, true); + fr.setFeaturePriority(data); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * hide feature group, then unhide + */ + fr.setGroupVisibility("group1", false); + assertNull(fr.getColour(sf1)); + fr.setGroupVisibility("group1", true); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * graduated colour by score, no threshold, no score + * + */ + FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, + Color.green, 1f, 11f); + fr.getFeatureColours().put("Cath", gc); + assertEquals(fr.getColour(sf1), Color.green); + + /* + * graduated colour by score, no threshold, with score value + */ + SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f, + "group1"); + // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0) + Color expected = new Color(255, 128, 0); + assertEquals(fr.getColour(sf2), expected); + + /* + * above threshold, score is above threshold - no change + */ + gc.setAboveThreshold(true); + gc.setThreshold(5f); + assertEquals(fr.getColour(sf2), expected); + + /* + * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11 + * or from yellow(255, 255, 0) to red(255, 0, 0) + */ + gc = new FeatureColour(Color.yellow, Color.red, Color.green, 5f, 11f); + fr.getFeatureColours().put("Cath", gc); + gc.setAutoScaled(false); // this does little other than save a checkbox setting! + assertEquals(fr.getColour(sf2), new Color(255, 213, 0)); + + /* + * feature score is below threshold - no colour + */ + gc.setAboveThreshold(true); + gc.setThreshold(7f); + assertNull(fr.getColour(sf2)); + + /* + * feature score is above threshold - no colour + */ + gc.setBelowThreshold(true); + gc.setThreshold(3f); + assertNull(fr.getColour(sf2)); + + /* + * colour by feature attribute value + * first with no value held + */ + gc = new FeatureColour(Color.yellow, Color.red, Color.green, 1f, 11f); + fr.getFeatureColours().put("Cath", gc); + gc.setAttributeName("AF"); + assertEquals(fr.getColour(sf2), Color.green); + + // with non-numeric attribute value + sf2.setValue("AF", "Five"); + assertEquals(fr.getColour(sf2), Color.green); + + // with numeric attribute value + sf2.setValue("AF", "6"); + assertEquals(fr.getColour(sf2), expected); + + // with numeric value outwith threshold + gc.setAboveThreshold(true); + gc.setThreshold(10f); + assertNull(fr.getColour(sf2)); + + // with filter on AF < 4 + gc.setAboveThreshold(false); + assertEquals(fr.getColour(sf2), expected); + FeatureMatcherSetI filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF")); + fr.setFeatureFilter("Cath", filter); + assertNull(fr.getColour(sf2)); + + // with filter on 'Consequence contains missense' + filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense", + "Consequence")); + fr.setFeatureFilter("Cath", filter); + // if feature has no Consequence attribute, no colour + assertNull(fr.getColour(sf2)); + // if attribute does not match filter, no colour + sf2.setValue("Consequence", "Synonymous"); + assertNull(fr.getColour(sf2)); + // attribute matches filter + sf2.setValue("Consequence", "Missense variant"); + assertEquals(fr.getColour(sf2), expected); + + // with filter on CSQ:Feature contains "ENST01234" + filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234", + "CSQ", "Feature")); + fr.setFeatureFilter("Cath", filter); + // if feature has no CSQ data, no colour + assertNull(fr.getColour(sf2)); + // if CSQ data does not include Feature, no colour + Map csqData = new HashMap<>(); + csqData.put("BIOTYPE", "Transcript"); + sf2.setValue("CSQ", csqData); + assertNull(fr.getColour(sf2)); + // if attribute does not match filter, no colour + csqData.put("Feature", "ENST9876"); + assertNull(fr.getColour(sf2)); + // attribute matches filter + csqData.put("Feature", "ENST01234"); + assertEquals(fr.getColour(sf2), expected); } } diff --git a/test/jalview/schemes/Blosum62ColourSchemeTest.java b/test/jalview/schemes/Blosum62ColourSchemeTest.java index 0b5b6bd..030a90f 100644 --- a/test/jalview/schemes/Blosum62ColourSchemeTest.java +++ b/test/jalview/schemes/Blosum62ColourSchemeTest.java @@ -20,7 +20,7 @@ public class Blosum62ColourSchemeTest * *