From: gmungoc Date: Wed, 15 Nov 2017 10:51:51 +0000 (+0000) Subject: JAL-2835 spike updated with latest X-Git-Tag: Release_2_11_0~62^2~13 X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=f8b17a9e7363b8a9e7cd12d61bc6d611c7c97d7d JAL-2835 spike updated with latest --- diff --git a/.classpath b/.classpath index 441ba60..c85feaf 100644 --- a/.classpath +++ b/.classpath @@ -66,8 +66,8 @@ - + diff --git a/benchmarking/README b/benchmarking/README index 60b94a9..fac0bf6 100644 --- a/benchmarking/README +++ b/benchmarking/README @@ -1,11 +1,13 @@ To set up benchmarking: -1. In the jalview directory run +You will need to install Maven: https://maven.apache.org/install.html + +1. Run the makedist target of build.xml in Eclipse, or in the jalview directory run ant makedist This builds a jalview.jar file and puts it into dist/ -2. Make a lib directory in benchmarking/ if not already present. +2. Make a lib directory in benchmarking/ if not already present and cd into this directory. 3. Purge any previous maven dependencies: mvn dependency:purge-local-repository -DactTransitively=false -DreResolve=false diff --git a/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java b/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java index eb35e3b..d3c67d7 100644 --- a/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java +++ b/benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java @@ -47,126 +47,128 @@ 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() - { - 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.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(); - } +{ + /* + * State with multiple hidden columns and a start position set + */ + @State(Scope.Thread) + public static class HiddenColsAndStartState + { + @Param({ "300", "10000", "100000" }) + public int maxcols; - @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) + @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() { - ColumnSelection sel = new ColumnSelection(); - tstate.h.revealAllHiddenColumns(sel); - return tstate.h; + 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.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; + } + } \ No newline at end of file diff --git a/build.xml b/build.xml index 436148a..89c3d24 100755 --- a/build.xml +++ b/build.xml @@ -451,10 +451,9 @@ - + j2se version="1.8+" - - + diff --git a/help/helpTOC.xml b/help/helpTOC.xml index 4636ea3..7ba4ee5 100755 --- a/help/helpTOC.xml +++ b/help/helpTOC.xml @@ -24,13 +24,7 @@ - - - - - - - + diff --git a/help/html/features/pdbseqfetcher.png b/help/html/features/pdbseqfetcher.png index 97a779a..2081a3d 100644 Binary files a/help/html/features/pdbseqfetcher.png and b/help/html/features/pdbseqfetcher.png differ diff --git a/help/html/features/pdbsequencefetcher.html b/help/html/features/pdbsequencefetcher.html index 2962ba6..bb63bed 100644 --- a/help/html/features/pdbsequencefetcher.html +++ b/help/html/features/pdbsequencefetcher.html @@ -37,7 +37,7 @@

To open the PDB Sequence Fetcher, select PDB as the database from any Sequence Fetcher dialog (opened via - "File →Fetch Sequences"). + "File →Fetch Sequences").

PDB sequence fetcher (introduced in Jalview 2.9) @@ -45,13 +45,18 @@

Searching the PDB Database

+

To search the PDB, begin typing in the text box. If the + 'autosearch' checkbox is enabled, then the results of your query + will be automatically updated and shown in the search results tab; + otherwise, press return to update the results. To access previous + searches, press the down-arrow or click the drop down menu icon at + the side of the search box. If you just want to paste in a list of + IDs, the 'Retrieve IDs' tab provides a batch-retrieval interface.

- To search the PDB, begin typing in the text box. The results of your - query are shown in the search results tab, which updates every time - you type in the search text box. You can sort results according to - the displayed columns, and select entries with the mouse and - keyboard. Once you have selected one or more entries, hit the OK - button to retrieve and view them in Jalview. + You can sort results according to the displayed columns, and select + entries with the mouse and keyboard. Once you have selected one or + more entries, hit the OK button to retrieve and + view them in Jalview.

    @@ -64,9 +69,8 @@ 1xyz:A
  • Bulk PDB retrieval
    Multiple PDB - IDs can be specified by separating them with a semi-colon.
    - e.g. 1xyz;2xyz;3xyz
    Hitting Return or OK will automatically - fetch those IDs, like the default Sequence Fetcher interface.
  • + IDs can be specified for retrieval via the + Retrieve IDs tab.
  • Wild card searching
    The following wild cards are supported by the EMBL-EBI PDBe query service: diff --git a/help/html/features/uniprotseqfetcher.png b/help/html/features/uniprotseqfetcher.png index a592e8e..23b55fa 100644 Binary files a/help/html/features/uniprotseqfetcher.png and b/help/html/features/uniprotseqfetcher.png differ diff --git a/help/html/features/uniprotsequencefetcher.html b/help/html/features/uniprotsequencefetcher.html index edd8995..4a64f52 100644 --- a/help/html/features/uniprotsequencefetcher.html +++ b/help/html/features/uniprotsequencefetcher.html @@ -46,10 +46,13 @@

    Searching the UniProt Database

    -

    - To search UniProt, simply begin typing in the text box. After a - short delay (about 1.5 seconds), results will be shown in the table - below. You can sort results by clicking on the displayed columns, +

    To search UniProt, simply begin typing in the text box. If the + 'autosearch' check box is enabled, then after a short delay (about + 1.5 seconds), results will be shown in the table below. Results are + also updated whenever you press Enter, and you can access previous + searches by pressing the 'Down' arrow or clicking the drop-down menu + icon at the side of the search box.

    +

    You can sort results by clicking on the displayed columns, and select entries with the mouse or keyboard. Once you have selected one or more entries, hit the OK button to retrieve the sequences. @@ -61,11 +64,8 @@

  • Bulk UniProt record retrieval
    To - retrieve several uniprot accessions at once, first select UniProt - ID from the dropdown menu, then paste in the accession IDs as a - semi-colon separated list. (e.g. fila_human; mnt_human; - mnt_mouse).
    Hitting Return or OK will automatically fetch - those IDs, like the default Sequence Fetcher interface.
  • + retrieve sequences for a list of Uniprot accessions, please enter + them via the 'Retrieve IDs' tab.
  • Complex queries with the UniProt query Syntax The text box also allows complex diff --git a/help/html/releases.html b/help/html/releases.html index 7be088e..6396313 100755 --- a/help/html/releases.html +++ b/help/html/releases.html @@ -71,7 +71,7 @@ li:before {
    2.10.3
    - 10/10/2017
    + 14/11/2017
    @@ -95,8 +95,15 @@ li:before {
  • Stop codons are excluded in CDS/Protein view from Ensembl locus cross-references
  • Start/End limits are shown in Pairwise Alignment report
  • +
  • Sequence fetcher's Free text 'autosearch' feature can be disabled
  • +
  • Retrieve IDs tab added for UniProt and PDB easier retrieval of sequences for lists of IDs
  • + +
+ Scripting +
    +
  • Groovy interpreter updated to 2.4.12
  • +
  • Example groovy script for generating a matrix of percent identity scores for current alignment.
-
  • Example groovy script for generating a matrix of percent identity scores for current alignment.
Testing and Deployment
  • Test to catch memory leaks in Jalview UI
@@ -133,6 +140,8 @@ li:before {
  • Alignment ruler height set incorrectly after canceling the Alignment Window's Font dialog
  • Show cross-references not enabled after restoring project until a new view is created
  • Warning popup about use of SEQUENCE_ID in URL links appears when only default EMBL-EBI link is configured (since 2.10.2b2)
  • +
  • Overview redraws whole window when box position is adjusted
  • +
  • Structure viewer doesn't map all chains in a multi-chain structure when viewing alignment involving more than one chain (since 2.10)
  • Applet
      @@ -144,6 +153,17 @@ li:before { BioJSON export does not preserve non-positional features
    + Known Java 9 Issues +
      +
    • Groovy Console very slow to open and is + not responsive when entering characters (Webstart, Java 9.01, + OSX 10.10) +
    • +
    + New Known Issues +
      +
    • +
    diff --git a/help/html/whatsNew.html b/help/html/whatsNew.html index 3475012..9cf1044 100755 --- a/help/html/whatsNew.html +++ b/help/html/whatsNew.html @@ -27,11 +27,18 @@ What's new in Jalview 2.10.3 ?

    - Version 2.10.3 is due for release in October 2017. The full list of + Version 2.10.3 was released in November 2017. The full list of bug fixes and new features can be found in the 2.10.3 Release Notes, but the highlights are below.

    +
      +
    • Faster import and more responsive UI when 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.
    • +

    Experimental Features

    diff --git a/lib/groovy-all-2.4.12-indy.jar b/lib/groovy-all-2.4.12-indy.jar new file mode 100644 index 0000000..bb246a3 Binary files /dev/null and b/lib/groovy-all-2.4.12-indy.jar differ diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 4626c39..9f1c71b 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -784,7 +784,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 ";" @@ -1349,3 +1349,7 @@ label.attribute = Attribute label.colour_by_label = Colour by label label.variable_colour = Variable colour label.select_new_colour = Select new colour +label.no_feature_attributes = No feature attributes found +option.enable_disable_autosearch = When ticked, search is performed automatically. +option.autosearch = Autosearch +label.retrieve_ids = Retrieve IDs diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index e8fd411..a7fff8e 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -708,7 +708,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 ";" diff --git a/src/jalview/api/FeatureColourI.java b/src/jalview/api/FeatureColourI.java index 3eebf6c..93773cc 100644 --- a/src/jalview/api/FeatureColourI.java +++ b/src/jalview/api/FeatureColourI.java @@ -189,17 +189,18 @@ public interface FeatureColourI boolean isColourByAttribute(); /** - * Answers the name of the attribute used for colouring if any, or null + * Answers the name of the attribute (and optional sub-attribute...) used for + * colouring if any, or null * * @return */ - String getAttributeName(); + String[] getAttributeName(); /** - * Sets the name of the attribute used for colouring if any, or null to remove - * this property + * 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); + void setAttributeName(String... name); } diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index 40c7d4d..ef0abbd 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -133,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 @@ -141,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 @@ -149,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 diff --git a/src/jalview/appletgui/FeatureColourChooser.java b/src/jalview/appletgui/FeatureColourChooser.java index 0d85c60..e9c377a 100644 --- a/src/jalview/appletgui/FeatureColourChooser.java +++ b/src/jalview/appletgui/FeatureColourChooser.java @@ -167,9 +167,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); diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index 1905f42..9e81d81 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -77,11 +77,6 @@ public class Sequence extends ASequence implements SequenceI */ Vector annotation; - /** - * The index of the sequence in a MSA - */ - int index = -1; - private SequenceFeaturesI sequenceFeatureStore; /* @@ -1739,30 +1734,6 @@ public class Sequence extends ASequence implements SequenceI } } - /** - * @return The index (zero-based) on this sequence in the MSA. It returns - * {@code -1} if this information is not available. - */ - @Override - public int getIndex() - { - return index; - } - - /** - * Defines the position of this sequence in the MSA. Use the value {@code -1} - * if this information is undefined. - * - * @param The - * position for this sequence. This value is zero-based (zero for - * this first sequence) - */ - @Override - public void setIndex(int value) - { - index = value; - } - @Override public void setRNA(RNA r) { diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java index 2110632..8a6cb61 100755 --- a/src/jalview/datamodel/SequenceFeature.java +++ b/src/jalview/datamodel/SequenceFeature.java @@ -59,18 +59,6 @@ public class SequenceFeature implements FeatureLocationI private static final String ROW_DATA = "%s%s%s"; /* - * map of otherDetails special keys, and their value fields' delimiter - */ - private static final Map INFO_KEYS = new HashMap<>(); - - static - { - INFO_KEYS.put("CSQ", ","); - // todo capture second level metadata (CSQ FORMAT) - // and delimiter "|" so as to report in a table within a table? - } - - /* * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as * name1=value1;name2=value2,value3;...etc */ @@ -184,7 +172,7 @@ public class SequenceFeature implements FeatureLocationI if (sf.otherDetails != null) { - otherDetails = new HashMap(); + otherDetails = new HashMap<>(); for (Entry entry : sf.otherDetails.entrySet()) { otherDetails.put(entry.getKey(), entry.getValue()); @@ -192,7 +180,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)); @@ -359,7 +347,7 @@ public class SequenceFeature implements FeatureLocationI { if (links == null) { - links = new Vector(); + links = new Vector<>(); } if (!links.contains(labelLink)) @@ -394,18 +382,25 @@ public class SequenceFeature implements FeatureLocationI /** * Answers the value of the specified attribute as string, or null if no such - * value + * 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) + public String getValueAsString(String... key) { if (otherDetails == null) { return null; } - Object value = otherDetails.get(key); + 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(); } @@ -438,7 +433,7 @@ public class SequenceFeature implements FeatureLocationI { if (otherDetails == null) { - otherDetails = new HashMap(); + otherDetails = new HashMap<>(); } otherDetails.put(key, value); @@ -463,8 +458,8 @@ public class SequenceFeature implements FeatureLocationI .getAttributeName(key); } - FeatureAttributes.getInstance().addAttribute(this.type, key, attDesc, - value.toString()); + FeatureAttributes.getInstance().addAttribute(this.type, attDesc, value, + key); } /* @@ -642,30 +637,33 @@ public class SequenceFeature implements FeatureLocationI { continue; // to avoid double reporting } - if (INFO_KEYS.containsKey(key)) + + Object value = entry.getValue(); + if (value instanceof Map) { /* - * split selected INFO data by delimiter over multiple lines + * expand values in a Map attribute across separate lines */ - String delimiter = INFO_KEYS.get(key); - String[] values = entry.getValue().toString().split(delimiter); - for (String value : values) + Map values = (Map) value; + for (Entry e : values.entrySet()) { - sb.append(String.format(ROW_DATA, key, "", value)); + sb.append(String.format(ROW_DATA, key, e.getKey().toString(), e + .getValue().toString())); } } else - { // tried but it failed to provide a tooltip :-( + { + // tried but it failed to provide a tooltip :-( String attDesc = null; if (metadata != null) { attDesc = metadata.getAttributeName(key); } - String value = entry.getValue().toString(); - if (isValueInteresting(key, value, metadata)) + String s = entry.getValue().toString(); + if (isValueInteresting(key, s, metadata)) { sb.append(String.format(ROW_DATA, key, attDesc == null ? "" - : attDesc, value)); + : attDesc, s)); } } } diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index 28be85f..c064373 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -450,17 +450,6 @@ public interface SequenceI extends ASequenceI public void transferAnnotation(SequenceI entry, Mapping mp); /** - * @param index - * The sequence index in the MSA - */ - public void setIndex(int index); - - /** - * @return The index of the sequence in the alignment - */ - public int getIndex(); - - /** * @return The RNA of the sequence in the alignment */ diff --git a/src/jalview/datamodel/features/FeatureAttributes.java b/src/jalview/datamodel/features/FeatureAttributes.java index 3dc4f19..7221d62 100644 --- a/src/jalview/datamodel/features/FeatureAttributes.java +++ b/src/jalview/datamodel/features/FeatureAttributes.java @@ -2,9 +2,11 @@ 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; /** @@ -14,7 +16,45 @@ public class FeatureAttributes { private static FeatureAttributes instance = new FeatureAttributes(); - private Map> attributes; + /* + * 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 { @@ -116,17 +156,19 @@ public class FeatureAttributes } /** - * Answers the attributes known for the given feature type, in alphabetical - * order (not case sensitive), or an empty set if no attributes are known + * 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) + public List getAttributes(String featureType) { if (!attributes.containsKey(featureType)) { - return Collections. emptyList(); + return Collections. emptyList(); } return new ArrayList<>(attributes.get(featureType).keySet()); @@ -156,23 +198,39 @@ public class FeatureAttributes * type, and updates the min-max for any numeric value * * @param featureType - * @param attName * @param description * @param value + * @param attName */ - public void addAttribute(String featureType, String attName, - String description, String value) + public void addAttribute(String featureType, String description, + Object value, String... attName) { if (featureType == null || attName == null) { return; } - Map atts = attributes.get(featureType); + /* + * 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( - String.CASE_INSENSITIVE_ORDER); + atts = new TreeMap<>(comparator); attributes.put(featureType, atts); } AttributeData attData = atts.get(attName); @@ -181,7 +239,7 @@ public class FeatureAttributes attData = new AttributeData(); atts.put(attName, attData); } - attData.addInstance(description, value); + attData.addInstance(description, valueAsString); } /** @@ -192,10 +250,10 @@ public class FeatureAttributes * @param attName * @return */ - public String getDescription(String featureType, String attName) + public String getDescription(String featureType, String... attName) { String desc = null; - Map atts = attributes.get(featureType); + Map atts = attributes.get(featureType); if (atts != null) { AttributeData attData = atts.get(attName); @@ -217,9 +275,9 @@ public class FeatureAttributes * @param attName * @return */ - public float[] getMinMax(String featureType, String attName) + public float[] getMinMax(String featureType, String... attName) { - Map atts = attributes.get(featureType); + Map atts = attributes.get(featureType); if (atts != null) { AttributeData attData = atts.get(attName); @@ -238,19 +296,18 @@ public class FeatureAttributes * @param attName * @param description */ - public void addDescription(String featureType, String attName, - String description) + public void addDescription(String featureType, String description, + String... attName) { if (featureType == null || attName == null) { return; } - Map atts = attributes.get(featureType); + Map atts = attributes.get(featureType); if (atts == null) { - atts = new TreeMap( - String.CASE_INSENSITIVE_ORDER); + atts = new TreeMap<>(comparator); attributes.put(featureType, atts); } AttributeData attData = atts.get(attName); diff --git a/src/jalview/ext/ensembl/EnsemblGenomes.java b/src/jalview/ext/ensembl/EnsemblGenomes.java index b40df50..bbd1f26 100644 --- a/src/jalview/ext/ensembl/EnsemblGenomes.java +++ b/src/jalview/ext/ensembl/EnsemblGenomes.java @@ -44,11 +44,13 @@ public class EnsemblGenomes extends EnsemblGene return "EnsemblGenomes"; } - private String Wrong[]; @Override public String getTestQuery() { - return "DDB_G0283883"; + /* + * Salmonella gene, Uniprot Q8Z9G6, EMBLCDS CAD01290 + */ + return "CAD01290"; } @Override diff --git a/src/jalview/fts/api/GFTSPanelI.java b/src/jalview/fts/api/GFTSPanelI.java index 99c0c51..974cc88 100644 --- a/src/jalview/fts/api/GFTSPanelI.java +++ b/src/jalview/fts/api/GFTSPanelI.java @@ -145,4 +145,11 @@ public interface GFTSPanelI * @return */ public String getCacheKey(); + + /** + * + * @return user preference name for configuring this FTS search's autosearch + * checkbox + */ + public String getAutosearchPreference(); } diff --git a/src/jalview/fts/core/GFTSPanel.java b/src/jalview/fts/core/GFTSPanel.java index c0d005f..9802d4b 100644 --- a/src/jalview/fts/core/GFTSPanel.java +++ b/src/jalview/fts/core/GFTSPanel.java @@ -56,6 +56,7 @@ import java.util.List; import javax.swing.ImageIcon; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JInternalFrame; @@ -88,6 +89,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI protected JInternalFrame mainFrame = new JInternalFrame( getFTSFrameTitle()); + protected JTabbedPane tabs = new JTabbedPane(); protected IProgressIndicator progressIndicator; protected JComboBox cmb_searchTarget = new JComboBox(); @@ -98,6 +100,8 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI protected JButton btn_cancel = new JButton(); + protected JCheckBox btn_autosearch = new JCheckBox(); + protected JvCacheableInputBox txt_search; protected SequenceFetcher seqFetcher; @@ -239,16 +243,38 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI public GFTSPanel() { + this(null); + } + + public GFTSPanel(SequenceFetcher fetcher) + { try { + if (fetcher == null) + { + tabs = null; + } jbInit(); + if (fetcher != null) + { + tabs.addTab(MessageManager.getString("label.retrieve_ids"), + fetcher); + fetcher.setDatabaseChooserVisible(false); + fetcher.embedWithFTSPanel(this); + } mainFrame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); + final JPanel ftsPanel = this; mainFrame.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { - txt_search.requestFocusInWindow(); + // TODO: make selected tab gain focus in correct widget + if (tabs != null + && tabs.getSelectedComponent() == ftsPanel) + { + txt_search.requestFocusInWindow(); + } } }); mainFrame.invalidate(); @@ -331,6 +357,20 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI } }); + btn_autosearch.setText(MessageManager.getString("option.autosearch")); + btn_autosearch.setToolTipText( + MessageManager.getString("option.enable_disable_autosearch")); + btn_autosearch.setSelected( + jalview.bin.Cache.getDefault(getAutosearchPreference(), true)); + btn_autosearch.addActionListener(new java.awt.event.ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + jalview.bin.Cache.setProperty(getAutosearchPreference(), + Boolean.toString(btn_autosearch.isSelected())); + } + }); btn_back.setFont(new java.awt.Font("Verdana", 0, 12)); btn_back.setText(MessageManager.getString("action.back")); btn_back.addActionListener(new java.awt.event.ActionListener() @@ -511,8 +551,14 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI if (primaryKeyName.equalsIgnoreCase(getCmbSearchTarget() .getSelectedItem().toString())) { + // TODO: nicer to show the list in the result set before + // viewing in Jalview perhaps ? transferToSequenceFetcher(getTypedText()); } + else + { + performSearchAction(); + } } } }); @@ -522,12 +568,10 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI @Override public void actionPerformed(ActionEvent e) { - String typed = getTypedText(); - if (!typed.equalsIgnoreCase(lastSearchTerm)) + if (btn_autosearch.isSelected() + || txt_search.wasEnterPressed()) { - searchAction(true); - paginatorCart.clear(); - lastSearchTerm = typed; + performSearchAction(); } } }, false); @@ -549,6 +593,15 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI } }); + txt_search.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + performSearchAction(); + } + }); final String searchTabTitle = MessageManager .getString("label.search_result"); final String configureCols = MessageManager @@ -613,6 +666,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI pnl_results.add(tabbedPane); pnl_inputs.add(cmb_searchTarget); pnl_inputs.add(txt_search); + pnl_inputs.add(btn_autosearch); pnl_inputs.add(lbl_loading); pnl_inputs.add(lbl_warning); pnl_inputs.add(lbl_blank); @@ -624,7 +678,17 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI this.add(pnl_results, java.awt.BorderLayout.CENTER); this.add(pnl_actions, java.awt.BorderLayout.SOUTH); mainFrame.setVisible(true); - mainFrame.setContentPane(this); + if (tabs != null) + { + tabs.setOpaque(true); + tabs.insertTab("Free Text Search", null, this, "", 0); + mainFrame.setContentPane(tabs); + tabs.setVisible(true); + } + else + { + mainFrame.setContentPane(this); + } mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); mainFrame.addInternalFrameListener( new javax.swing.event.InternalFrameAdapter() @@ -635,8 +699,6 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI closeAction(); } }); - mainFrame.setVisible(true); - mainFrame.setContentPane(this); mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); Integer x = getTempUserPrefs().get("FTSPanel.x"); Integer y = getTempUserPrefs().get("FTSPanel.y"); @@ -698,6 +760,18 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI } + void performSearchAction() + { + String typed = getTypedText(); + if (typed != null && typed.length() > 0 + && !typed.equalsIgnoreCase(lastSearchTerm)) + { + searchAction(true); + paginatorCart.clear(); + lastSearchTerm = typed; + } + } + public boolean wantedFieldsUpdated() { if (previousWantedFields == null) @@ -774,7 +848,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI } } - protected void btn_back_ActionPerformed() + public void btn_back_ActionPerformed() { closeAction(); new SequenceFetcher(progressIndicator); @@ -787,7 +861,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI btn_cancel.setEnabled(false); } - protected void btn_cancel_ActionPerformed() + public void btn_cancel_ActionPerformed() { closeAction(); } diff --git a/src/jalview/fts/service/pdb/PDBFTSPanel.java b/src/jalview/fts/service/pdb/PDBFTSPanel.java index 19f7db4..053d91b 100644 --- a/src/jalview/fts/service/pdb/PDBFTSPanel.java +++ b/src/jalview/fts/service/pdb/PDBFTSPanel.java @@ -43,9 +43,11 @@ public class PDBFTSPanel extends GFTSPanel private static final String PDB_FTS_CACHE_KEY = "CACHE.PDB_FTS"; + private static final String PDB_AUTOSEARCH = "FTS.PDB.AUTOSEARCH"; + public PDBFTSPanel(SequenceFetcher fetcher) { - super(); + super(fetcher); pageLimit = PDBFTSRestClient.getInstance().getDefaultResponsePageSize(); this.seqFetcher = fetcher; this.progressIndicator = (fetcher == null) ? null @@ -281,4 +283,9 @@ public class PDBFTSPanel extends GFTSPanel return PDB_FTS_CACHE_KEY; } -} + @Override + public String getAutosearchPreference() + { + return PDB_AUTOSEARCH; + } +} \ No newline at end of file diff --git a/src/jalview/fts/service/uniprot/UniprotFTSPanel.java b/src/jalview/fts/service/uniprot/UniprotFTSPanel.java index 020331a..df54dea 100644 --- a/src/jalview/fts/service/uniprot/UniprotFTSPanel.java +++ b/src/jalview/fts/service/uniprot/UniprotFTSPanel.java @@ -44,9 +44,11 @@ public class UniprotFTSPanel extends GFTSPanel private static final String UNIPROT_FTS_CACHE_KEY = "CACHE.UNIPROT_FTS"; + private static final String UNIPROT_AUTOSEARCH = "FTS.UNIPROT.AUTOSEARCH"; + public UniprotFTSPanel(SequenceFetcher fetcher) { - super(); + super(fetcher); pageLimit = UniProtFTSRestClient.getInstance() .getDefaultResponsePageSize(); this.seqFetcher = fetcher; @@ -237,4 +239,10 @@ public class UniprotFTSPanel extends GFTSPanel { return UNIPROT_FTS_CACHE_KEY; } + + @Override + public String getAutosearchPreference() + { + return UNIPROT_AUTOSEARCH; + } } diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 90271c8..4d09084 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -583,58 +583,6 @@ public class AlignViewport extends AlignmentViewport .getStructureSelectionManager(Desktop.instance); } - /** - * - * @param pdbEntries - * @return an array of SequenceI arrays, one for each PDBEntry, listing which - * sequences in the alignment hold a reference to it - */ - public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries) - { - List seqvectors = new ArrayList(); - for (PDBEntry pdb : pdbEntries) - { - List choosenSeqs = new ArrayList(); - for (SequenceI sq : alignment.getSequences()) - { - Vector pdbRefEntries = sq.getDatasetSequence() - .getAllPDBEntries(); - if (pdbRefEntries == null) - { - continue; - } - for (PDBEntry pdbRefEntry : pdbRefEntries) - { - if (pdbRefEntry.getId().equals(pdb.getId())) - { - if (pdbRefEntry.getChainCode() != null - && pdb.getChainCode() != null) - { - if (pdbRefEntry.getChainCode().equalsIgnoreCase( - pdb.getChainCode()) && !choosenSeqs.contains(sq)) - { - choosenSeqs.add(sq); - continue; - } - } - else - { - if (!choosenSeqs.contains(sq)) - { - choosenSeqs.add(sq); - continue; - } - } - - } - } - } - seqvectors - .add(choosenSeqs.toArray(new SequenceI[choosenSeqs.size()])); - } - return seqvectors.toArray(new SequenceI[seqvectors.size()][]); - } - @Override public boolean isNormaliseSequenceLogo() { diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java index 6280a64..da3819c 100644 --- a/src/jalview/gui/FeatureColourChooser.java +++ b/src/jalview/gui/FeatureColourChooser.java @@ -57,8 +57,16 @@ import javax.swing.event.ChangeListener; public class FeatureColourChooser extends JalviewDialog { + private static final String COLON = ":"; + private static final int MAX_TOOLTIP_LENGTH = 50; + private static int NO_COLOUR_OPTION = 0; + + private static int MIN_COLOUR_OPTION = 1; + + private static int MAX_COLOUR_OPTION = 2; + private FeatureRenderer fr; private FeatureColourI cs; @@ -157,9 +165,8 @@ public class FeatureColourChooser extends JalviewDialog this.fr = frender; this.type = theType; ap = fr.ap; - String title = MessageManager - .formatMessage("label.graduated_color_for_params", new String[] - { theType }); + String title = MessageManager.formatMessage("label.variable_color_for", + new String[] { theType }); initDialogFrame(this, true, blocking, title, 470, 300); slider.addChangeListener(new ChangeListener() @@ -190,6 +197,7 @@ public class FeatureColourChooser extends JalviewDialog } }); + // todo move all threshold setup inside a method float mm[] = fr.getMinMax().get(theType)[0]; min = mm[0]; max = mm[1]; @@ -231,6 +239,8 @@ public class FeatureColourChooser extends JalviewDialog } minColour.setBackground(oldminColour = cs.getMinColour()); maxColour.setBackground(oldmaxColour = cs.getMaxColour()); + noColour = cs.getNoColour(); + adjusting = true; try @@ -245,15 +255,15 @@ public class FeatureColourChooser extends JalviewDialog /* * set the initial state of options on screen */ - thresholdIsMin.setSelected(!cs.isAutoScaled()); - if (cs.isColourByLabel()) { if (cs.isColourByAttribute()) { byAttributeText.setSelected(true); textAttributeCombo.setEnabled(true); - textAttributeCombo.setSelectedItem(cs.getAttributeName()); + String[] attributeName = cs.getAttributeName(); + textAttributeCombo + .setSelectedItem(String.join(COLON, attributeName)); } else { @@ -266,8 +276,9 @@ public class FeatureColourChooser extends JalviewDialog if (cs.isColourByAttribute()) { byAttributeValue.setSelected(true); - String attributeName = cs.getAttributeName(); - valueAttributeCombo.setSelectedItem(attributeName); + String[] attributeName = cs.getAttributeName(); + valueAttributeCombo + .setSelectedItem(String.join(COLON, attributeName)); valueAttributeCombo.setEnabled(true); updateMinMax(); } @@ -278,6 +289,19 @@ public class FeatureColourChooser extends JalviewDialog } } + if (noColour == null) + { + noValueCombo.setSelectedIndex(NO_COLOUR_OPTION); + } + else if (noColour.equals(oldminColour)) + { + noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION); + } + else if (noColour.equals(oldmaxColour)) + { + noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION); + } + threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black); threshline.value = cs.getThreshold(); @@ -369,7 +393,8 @@ public class FeatureColourChooser extends JalviewDialog else if (byAttributeValue.isSelected()) { String attName = (String) valueAttributeCombo.getSelectedItem(); - minMax = FeatureAttributes.getInstance().getMinMax(type, attName); + String[] attNames = attName.split(COLON); + minMax = FeatureAttributes.getInstance().getMinMax(type, attNames); } if (minMax != null) { @@ -408,8 +433,8 @@ public class FeatureColourChooser extends JalviewDialog byAttributeValue.addActionListener(changeMinMaxAction); byWhatPanel.add(byAttributeValue); - List attNames = FeatureAttributes.getInstance().getAttributes( - type); + List attNames = FeatureAttributes.getInstance() + .getAttributes(type); valueAttributeCombo = populateAttributesDropdown(type, attNames, true); /* @@ -555,21 +580,21 @@ public class FeatureColourChooser extends JalviewDialog } /** - * Action on user choice of no / min / max colour when there is no value to - * colour by + * Action on user choice of no / min / max colour to use when there is no + * value to colour by */ protected void setNoValueColour() { int i = noValueCombo.getSelectedIndex(); - if (i == 0) + if (i == NO_COLOUR_OPTION) { noColour = null; } - else if (i == 1) + else if (i == MIN_COLOUR_OPTION) { noColour = minColour.getBackground(); } - else if (i == 2) + else if (i == MAX_COLOUR_OPTION) { noColour = maxColour.getBackground(); } @@ -612,8 +637,8 @@ public class FeatureColourChooser extends JalviewDialog byAttributeText.addActionListener(changeColourAction); byTextPanel.add(byAttributeText); - List attNames = FeatureAttributes.getInstance().getAttributes( - type); + List attNames = FeatureAttributes.getInstance() + .getAttributes(type); textAttributeCombo = populateAttributesDropdown(type, attNames, false); byTextPanel.add(textAttributeCombo); @@ -715,13 +740,18 @@ public class FeatureColourChooser extends JalviewDialog { attribute = (String) textAttributeCombo.getSelectedItem(); textAttributeCombo.setEnabled(true); + acg.setAttributeName(attribute.split(COLON)); } else if (byAttributeValue.isSelected()) { attribute = (String) valueAttributeCombo.getSelectedItem(); valueAttributeCombo.setEnabled(true); + acg.setAttributeName(attribute.split(COLON)); + } + else + { + acg.setAttributeName((String) null); } - acg.setAttributeName(attribute); if (!hasThreshold) { @@ -798,6 +828,7 @@ public class FeatureColourChooser extends JalviewDialog maxColour.setForeground(oldmaxColour); minColour.setBackground(oldminColour); minColour.setForeground(oldminColour); + noColour = oldNoColour; } /* @@ -912,23 +943,26 @@ public class FeatureColourChooser extends JalviewDialog /** * A helper method to build the drop-down choice of attributes for a feature. * Where metadata is available with a description for an attribute, that is - * added as a tooltip. The list may be restricted to attributes for which we - * hold a range of numerical values (so suitable candidates for a graduated - * colour scheme). + * added as a tooltip. The list may optionally be restricted to attributes for + * which we hold a range of numerical values (so suitable candidates for a + * graduated colour scheme). + *

    + * 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. * * @param featureType * @param attNames * @param withNumericRange */ protected JComboBox populateAttributesDropdown( - String featureType, List attNames, + String featureType, List attNames, boolean withNumericRange) { List validAtts = new ArrayList<>(); List tooltips = new ArrayList<>(); FeatureAttributes fa = FeatureAttributes.getInstance(); - for (String attName : attNames) + for (String[] attName : attNames) { if (withNumericRange) { @@ -938,7 +972,7 @@ public class FeatureColourChooser extends JalviewDialog continue; } } - validAtts.add(attName); + validAtts.add(String.join(COLON, attName)); String desc = fa.getDescription(featureType, attName); if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) { diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 4e4d2cb..ed98830 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -53,6 +53,7 @@ import java.awt.FlowLayout; import java.awt.Font; import java.awt.Graphics; import java.awt.GridLayout; +import java.awt.LayoutManager; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -119,6 +120,8 @@ import javax.swing.table.TableCellRenderer; public class FeatureSettings extends JPanel implements FeatureSettingsControllerI { + private static final String COLON = ":"; + private static final int MIN_WIDTH = 400; private static final int MIN_HEIGHT = 400; @@ -199,6 +202,9 @@ public class FeatureSettings extends JPanel private JTextArea filtersAsText; + // set white normally, black to debug layout + private Color debugBorderColour = Color.white; + /** * Constructor * @@ -1371,6 +1377,7 @@ public class FeatureSettings extends JPanel */ JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); andOrPanel.setBackground(Color.white); + andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour)); andFilters = new JRadioButton("And"); orFilters = new JRadioButton("Or"); ActionListener actionListener = new ActionListener() @@ -1397,8 +1404,9 @@ public class FeatureSettings extends JPanel * panel with filters - populated by refreshFiltersDisplay */ chooseFiltersPanel = new JPanel(); - chooseFiltersPanel.setLayout(new BoxLayout(chooseFiltersPanel, - BoxLayout.Y_AXIS)); + LayoutManager box = new BoxLayout(chooseFiltersPanel, + BoxLayout.Y_AXIS); + chooseFiltersPanel.setLayout(box); filtersPanel.add(chooseFiltersPanel); /* @@ -1454,12 +1462,12 @@ public class FeatureSettings extends JPanel } if (!found) { - filteredFeatureChoice // todo i18n - .addItem("No filterable feature attributes known"); + filteredFeatureChoice.addItem(MessageManager + .getString("label.no_feature_attributes")); + filteredFeatureChoice.setEnabled(false); } filteredFeatureChoice.addItemListener(listener); - } /** @@ -1480,8 +1488,8 @@ public class FeatureSettings extends JPanel * look up attributes known for feature type */ String selectedType = (String) filteredFeatureChoice.getSelectedItem(); - List attNames = FeatureAttributes.getInstance().getAttributes( - selectedType); + List attNames = FeatureAttributes.getInstance() + .getAttributes(selectedType); /* * if this feature type has filters set, load them first @@ -1501,7 +1509,8 @@ public class FeatureSettings extends JPanel /* * and an empty filter for the user to populate (add) */ - KeyedMatcherI noFilter = new KeyedMatcher("", Condition.values()[0], ""); + KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "", + (String) null); filters.add(noFilter); /* @@ -1510,14 +1519,16 @@ public class FeatureSettings extends JPanel int filterIndex = 0; for (KeyedMatcherI filter : filters) { - String key = filter.getKey(); + String[] attName = filter.getKey(); Condition condition = filter.getMatcher() .getCondition(); String pattern = filter.getMatcher().getPattern(); - JPanel row = addFilter(key, attNames, condition, pattern, filterIndex); + JPanel row = addFilter(attName, attNames, condition, pattern, filterIndex); + row.setBorder(BorderFactory.createLineBorder(debugBorderColour)); chooseFiltersPanel.add(row); filterIndex++; } + // chooseFiltersPanel.add(Box.createVerticalGlue()); filtersPane.validate(); filtersPane.repaint(); @@ -1531,27 +1542,24 @@ public class FeatureSettings extends JPanel *

  • a text field for input of a match pattern
  • *
  • optionally, a 'remove' button
  • * - * If attribute, condition or pattern are not null, they are set as defaults - * for the input fields. The 'remove' button is added unless the pattern is - * null or empty (incomplete filter condition). + * If attribute, condition or pattern are not null, they are set as defaults for + * the input fields. The 'remove' button is added unless the pattern is null or + * empty (incomplete filter condition). * - * @param attribute + * @param attName * @param attNames * @param cond * @param pattern * @param filterIndex * @return */ - protected JPanel addFilter(String attribute, List attNames, + protected JPanel addFilter(String[] attName, List attNames, Condition cond, String pattern, int filterIndex) { JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); filterRow.setBackground(Color.white); /* - * inputs for attribute, condition, pattern - */ - /* * drop-down choice of attribute, with description as a tooltip * if we can obtain it */ @@ -1588,13 +1596,13 @@ public class FeatureSettings extends JPanel } }; - if ("".equals(attribute)) + if (attName == null) // the 'add a condition' row { attCombo.setSelectedItem(null); } else { - attCombo.setSelectedItem(attribute); + attCombo.setSelectedItem(String.join(COLON, attName)); } attCombo.addItemListener(itemListener); @@ -1634,7 +1642,7 @@ public class FeatureSettings extends JPanel */ if (pattern != null && pattern.trim().length() > 0) { - // todo: gif for - button + // todo: gif for button drawing '-' or 'x' JButton removeCondition = new BasicArrowButton(SwingConstants.WEST); removeCondition.setToolTipText(MessageManager .getString("label.delete_row")); @@ -1662,22 +1670,24 @@ public class FeatureSettings extends JPanel * @param attNames */ protected JComboBox populateAttributesDropdown( - String featureType, List attNames) + String featureType, List attNames) { + List displayNames = new ArrayList<>(); List tooltips = new ArrayList<>(); FeatureAttributes fa = FeatureAttributes.getInstance(); - for (String attName : attNames) + for (String[] attName : attNames) { String desc = fa.getDescription(featureType, attName); if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) { desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "..."; } + displayNames.add(String.join(COLON, attName)); tooltips.add(desc == null ? "" : desc); } JComboBox attCombo = JvSwingUtils.buildComboWithTooltips( - attNames, tooltips); + displayNames, tooltips); if (attNames.isEmpty()) { attCombo.setToolTipText(MessageManager @@ -1755,7 +1765,8 @@ public class FeatureSettings extends JPanel String attName = (String) attCombo.getSelectedItem(); Condition cond = (Condition) condCombo.getSelectedItem(); String pattern = valueField.getText(); - KeyedMatcherI km = new KeyedMatcher(attName, cond, pattern); + KeyedMatcherI km = new KeyedMatcher(cond, pattern, + attName.split(COLON)); filters.set(filterIndex, km); } @@ -2129,7 +2140,7 @@ public class FeatureSettings extends JPanel if (gcol.isColourByAttribute()) { - tx.append(gcol.getAttributeName()); + tx.append(String.join(":", gcol.getAttributeName())); } else if (!gcol.isColourByLabel()) { diff --git a/src/jalview/gui/OverviewCanvas.java b/src/jalview/gui/OverviewCanvas.java index 7371eb5..2991889 100644 --- a/src/jalview/gui/OverviewCanvas.java +++ b/src/jalview/gui/OverviewCanvas.java @@ -183,6 +183,8 @@ public class OverviewCanvas extends JComponent @Override public void paintComponent(Graphics g) { + // super.paintComponent(g); + if (restart) { if (lastMiniMe == null) @@ -204,7 +206,8 @@ public class OverviewCanvas extends JComponent && ((getWidth() != od.getWidth()) || (getHeight() != od.getHeight()))) { - // if there is annotation, scale the alignment and annotation separately + // if there is annotation, scale the alignment and annotation + // separately if (od.getGraphHeight() > 0) { BufferedImage topImage = lastMiniMe.getSubimage(0, 0, @@ -235,25 +238,24 @@ public class OverviewCanvas extends JComponent od.setHeight(getHeight()); } - // scale lastMiniMe to the new size - g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); - // make sure the box is in the right place od.setBoxPosition(av.getAlignment().getHiddenSequences(), av.getAlignment().getHiddenColumns()); } - else // not a resize - { - // fall back to normal behaviour - g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); - } + // fall back to normal behaviour + g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); } - + else + { + g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); + } + // draw the box g.setColor(Color.red); od.drawBox(g); } + public void dispose() { dispose = true; diff --git a/src/jalview/gui/OverviewPanel.java b/src/jalview/gui/OverviewPanel.java index 9ddb751..43b4310 100755 --- a/src/jalview/gui/OverviewPanel.java +++ b/src/jalview/gui/OverviewPanel.java @@ -329,6 +329,22 @@ public class OverviewPanel extends JPanel * changed * */ + private void setBoxPositionOnly() + { + if (od != null) + { + int oldX = od.getBoxX(); + int oldY = od.getBoxY(); + int oldWidth = od.getBoxWidth(); + int oldHeight = od.getBoxHeight(); + od.setBoxPosition(av.getAlignment().getHiddenSequences(), + av.getAlignment().getHiddenColumns()); + repaint(oldX - 1, oldY - 1, oldWidth + 2, oldHeight + 2); + repaint(od.getBoxX(), od.getBoxY(), od.getBoxWidth(), + od.getBoxHeight()); + } + } + private void setBoxPosition() { if (od != null) @@ -342,7 +358,7 @@ public class OverviewPanel extends JPanel @Override public void propertyChange(PropertyChangeEvent evt) { - setBoxPosition(); + setBoxPositionOnly(); } /** diff --git a/src/jalview/gui/SequenceFetcher.java b/src/jalview/gui/SequenceFetcher.java index e05230b..8d46792 100755 --- a/src/jalview/gui/SequenceFetcher.java +++ b/src/jalview/gui/SequenceFetcher.java @@ -26,6 +26,7 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.fts.core.GFTSPanel; import jalview.fts.service.pdb.PDBFTSPanel; import jalview.fts.service.uniprot.UniprotFTSPanel; import jalview.io.FileFormatI; @@ -78,6 +79,8 @@ public class SequenceFetcher extends JPanel implements Runnable JButton close = new JButton(); + JButton back = new JButton(); + JPanel jPanel1 = new JPanel(); JTextArea textArea = new JTextArea(); @@ -383,6 +386,15 @@ public class SequenceFetcher extends JPanel implements Runnable .getString("label.additional_sequence_fetcher")); } + GFTSPanel parentFTSframe = null; + /** + * change the buttons so they fit with the FTS panel. + */ + public void embedWithFTSPanel(GFTSPanel toClose) + { + back.setVisible(true); + parentFTSframe = toClose; + } private void jbInit() throws Exception { this.setLayout(borderLayout2); @@ -427,7 +439,7 @@ public class SequenceFetcher extends JPanel implements Runnable example_actionPerformed(); } }); - close.setText(MessageManager.getString("action.close")); + close.setText(MessageManager.getString("action.cancel")); close.addActionListener(new ActionListener() { @Override @@ -436,6 +448,17 @@ public class SequenceFetcher extends JPanel implements Runnable close_actionPerformed(e); } }); + back.setText(MessageManager.getString("action.back")); + back.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + parentFTSframe.btn_back_ActionPerformed(); + } + }); + // back not visible unless embedded + back.setVisible(false); textArea.setFont(JvSwingUtils.getLabelFont()); textArea.setLineWrap(true); textArea.addKeyListener(new KeyAdapter() @@ -451,9 +474,10 @@ public class SequenceFetcher extends JPanel implements Runnable }); jPanel3.setLayout(borderLayout1); borderLayout1.setVgap(5); - jPanel1.add(ok); + jPanel1.add(back); jPanel1.add(example); jPanel1.add(clear); + jPanel1.add(ok); jPanel1.add(close); jPanel2.setLayout(borderLayout3); databaseButt = /*database.getDatabaseSelectorButton(); @@ -582,6 +606,10 @@ public class SequenceFetcher extends JPanel implements Runnable try { frame.setClosed(true); + if (parentFTSframe!=null) + { + parentFTSframe.btn_cancel_ActionPerformed(); + } } catch (Exception ex) { } @@ -594,7 +622,7 @@ public class SequenceFetcher extends JPanel implements Runnable textArea.setEnabled(false); ok.setEnabled(false); close.setEnabled(false); - + back.setEnabled(false); Thread worker = new Thread(this); worker.start(); } @@ -606,6 +634,7 @@ public class SequenceFetcher extends JPanel implements Runnable textArea.setEnabled(true); ok.setEnabled(true); close.setEnabled(true); + back.setEnabled(parentFTSframe != null); } @Override @@ -1087,4 +1116,9 @@ public class SequenceFetcher extends JPanel implements Runnable { frame.setVisible(false); } + + public void setDatabaseChooserVisible(boolean b) + { + databaseButt.setVisible(b); + } } diff --git a/src/jalview/gui/StructureChooser.java b/src/jalview/gui/StructureChooser.java index 20f4a49..37632ef 100644 --- a/src/jalview/gui/StructureChooser.java +++ b/src/jalview/gui/StructureChooser.java @@ -927,16 +927,10 @@ public class StructureChooser extends GStructureChooser } if (pdbEntriesToView.length > 1) { - ArrayList seqsMap = new ArrayList<>(); - for (SequenceI seq : sequences) - { - seqsMap.add(new SequenceI[] { seq }); - } - SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]); - - setProgressBar(MessageManager - .getString("status.fetching_3d_structures_for_selected_entries"), progressId); - sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel); + setProgressBar(MessageManager.getString( + "status.fetching_3d_structures_for_selected_entries"), + progressId); + sViewer.viewStructures(pdbEntriesToView, sequences, alignPanel); } else { diff --git a/src/jalview/gui/StructureViewer.java b/src/jalview/gui/StructureViewer.java index e58b378..b142613 100644 --- a/src/jalview/gui/StructureViewer.java +++ b/src/jalview/gui/StructureViewer.java @@ -29,19 +29,24 @@ import jalview.structure.StructureSelectionManager; import java.awt.Rectangle; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; /** - * proxy for handling structure viewers. - * - * this allows new views to be created with the currently configured viewer, the - * preferred viewer to be set/read and existing views created previously with a - * particular viewer to be recovered + * A proxy for handling structure viewers, that orchestrates adding selected + * structures, associated with sequences in Jalview, to an existing viewer, or + * opening a new one. Currently supports either Jmol or Chimera as the structure + * viewer. * * @author jprocter */ public class StructureViewer { + private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type "; + StructureSelectionManager ssm; public enum ViewerType @@ -49,6 +54,16 @@ public class StructureViewer JMOL, CHIMERA }; + /** + * Constructor + * + * @param structureSelectionManager + */ + public StructureViewer(StructureSelectionManager structureSelectionManager) + { + ssm = structureSelectionManager; + } + public ViewerType getViewerType() { String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY, @@ -61,135 +76,157 @@ public class StructureViewer Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name()); } - public StructureViewer( - StructureSelectionManager structureSelectionManager) - { - ssm = structureSelectionManager; - } - /** * View multiple PDB entries, each with associated sequences * * @param pdbs - * @param seqsForPdbs + * @param seqs * @param ap * @return */ public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs, - SequenceI[][] seqsForPdbs, AlignmentPanel ap) + SequenceI[] seqs, AlignmentPanel ap) { - JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqsForPdbs, ap); + JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap); if (viewer != null) { + /* + * user added structure to an existing viewer - all done + */ return viewer; } - return viewStructures(getViewerType(), pdbs, seqsForPdbs, ap); + + ViewerType viewerType = getViewerType(); + + Map seqsForPdbs = getSequencesForPdbs(pdbs, + seqs); + PDBEntry[] pdbsForFile = seqsForPdbs.keySet().toArray( + new PDBEntry[seqsForPdbs.size()]); + SequenceI[][] theSeqs = seqsForPdbs.values().toArray( + new SequenceI[seqsForPdbs.size()][]); + JalviewStructureDisplayI sview = null; + if (viewerType.equals(ViewerType.JMOL)) + { + sview = new AppJmol(ap, pdbsForFile, theSeqs); + } + else if (viewerType.equals(ViewerType.CHIMERA)) + { + sview = new ChimeraViewFrame(pdbsForFile, theSeqs, ap); + } + else + { + Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString()); + } + return sview; } /** - * A strictly temporary method pending JAL-1761 refactoring. Determines if all - * the passed PDB entries are the same (this is the case if selected sequences - * to view structure for are chains of the same structure). If so, calls the - * single-pdb version of viewStructures and returns the viewer, else returns - * null. + * Converts the list of selected PDB entries (possibly including duplicates + * for multiple chains), and corresponding sequences, into a map of sequences + * for each distinct PDB file. Returns null if either argument is null, or + * their lengths do not match. * * @param pdbs - * @param seqsForPdbs - * @param ap + * @param seqs * @return */ - private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs, - SequenceI[][] seqsForPdbs, AlignmentPanel ap) + Map getSequencesForPdbs(PDBEntry[] pdbs, + SequenceI[] seqs) { - List seqs = new ArrayList(); - if (pdbs == null || pdbs.length == 0) + if (pdbs == null || seqs == null || pdbs.length != seqs.length) { return null; } - int i = 0; - String firstFile = pdbs[0].getFile(); - for (PDBEntry pdb : pdbs) + + /* + * we want only one 'representative' PDBEntry per distinct file name + * (there may be entries for distinct chains) + */ + Map pdbsSeen = new HashMap<>(); + + /* + * LinkedHashMap preserves order of PDB entries (significant if they + * will get superimposed to the first structure) + */ + Map> pdbSeqs = new LinkedHashMap<>(); + for (int i = 0; i < pdbs.length; i++) { + PDBEntry pdb = pdbs[i]; + SequenceI seq = seqs[i]; String pdbFile = pdb.getFile(); - if (pdbFile == null || !pdbFile.equals(firstFile)) + if (!pdbsSeen.containsKey(pdbFile)) { - return null; + pdbsSeen.put(pdbFile, pdb); + pdbSeqs.put(pdb, new ArrayList()); + } + else + { + pdb = pdbsSeen.get(pdbFile); } - SequenceI[] pdbseqs = seqsForPdbs[i++]; - if (pdbseqs != null) + List seqsForPdb = pdbSeqs.get(pdb); + if (!seqsForPdb.contains(seq)) { - for (SequenceI sq : pdbseqs) - { - seqs.add(sq); - } + seqsForPdb.add(seq); } } - return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]), - ap); - } - - public JalviewStructureDisplayI viewStructures(PDBEntry pdb, - SequenceI[] seqsForPdb, AlignmentPanel ap) - { - return viewStructures(getViewerType(), pdb, seqsForPdb, ap); - } - protected JalviewStructureDisplayI viewStructures(ViewerType viewerType, - PDBEntry[] pdbs, SequenceI[][] seqsForPdbs, AlignmentPanel ap) - { - PDBEntry[] pdbsForFile = getUniquePdbFiles(pdbs); - JalviewStructureDisplayI sview = null; - if (viewerType.equals(ViewerType.JMOL)) - { - sview = new AppJmol(ap, pdbsForFile, - ap.av.collateForPDB(pdbsForFile)); - } - else if (viewerType.equals(ViewerType.CHIMERA)) + /* + * convert to Map + */ + Map result = new LinkedHashMap<>(); + for (Entry> entry : pdbSeqs.entrySet()) { - sview = new ChimeraViewFrame(pdbsForFile, - ap.av.collateForPDB(pdbsForFile), ap); + List theSeqs = entry.getValue(); + result.put(entry.getKey(), + theSeqs.toArray(new SequenceI[theSeqs.size()])); } - else - { - Cache.log.error("Unknown structure viewer type " - + getViewerType().toString()); - } - return sview; + + return result; } /** - * Convert the array of PDBEntry into an array with no filename repeated + * A strictly temporary method pending JAL-1761 refactoring. Determines if all + * the passed PDB entries are the same (this is the case if selected sequences + * to view structure for are chains of the same structure). If so, calls the + * single-pdb version of viewStructures and returns the viewer, else returns + * null. * * @param pdbs + * @param seqsForPdbs + * @param ap * @return */ - static PDBEntry[] getUniquePdbFiles(PDBEntry[] pdbs) + private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs, + SequenceI[] seqsForPdbs, AlignmentPanel ap) { - if (pdbs == null) + List seqs = new ArrayList(); + if (pdbs == null || pdbs.length == 0) { return null; } - List uniques = new ArrayList(); - List filesSeen = new ArrayList(); - for (PDBEntry entry : pdbs) + int i = 0; + String firstFile = pdbs[0].getFile(); + for (PDBEntry pdb : pdbs) { - String file = entry.getFile(); - if (file == null) + String pdbFile = pdb.getFile(); + if (pdbFile == null || !pdbFile.equals(firstFile)) { - uniques.add(entry); + return null; } - else if (!filesSeen.contains(file)) + SequenceI pdbseq = seqsForPdbs[i++]; + if (pdbseq != null) { - uniques.add(entry); - filesSeen.add(file); + seqs.add(pdbseq); } } - return uniques.toArray(new PDBEntry[uniques.size()]); + return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]), + ap); } - protected JalviewStructureDisplayI viewStructures(ViewerType viewerType, - PDBEntry pdb, SequenceI[] seqsForPdb, AlignmentPanel ap) + public JalviewStructureDisplayI viewStructures(PDBEntry pdb, + SequenceI[] seqsForPdb, AlignmentPanel ap) { + ViewerType viewerType = getViewerType(); JalviewStructureDisplayI sview = null; if (viewerType.equals(ViewerType.JMOL)) { @@ -201,8 +238,7 @@ public class StructureViewer } else { - Cache.log.error("Unknown structure viewer type " - + getViewerType().toString()); + Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString()); } return sview; } @@ -242,7 +278,7 @@ public class StructureViewer "Unsupported structure viewer type " + type.toString()); break; default: - Cache.log.error("Unknown structure viewer type " + type.toString()); + Cache.log.error(UNKNOWN_VIEWER_TYPE + type.toString()); } return sview; } diff --git a/src/jalview/io/AlignFile.java b/src/jalview/io/AlignFile.java index a603cca..2340283 100755 --- a/src/jalview/io/AlignFile.java +++ b/src/jalview/io/AlignFile.java @@ -174,11 +174,6 @@ public abstract class AlignFile extends FileParse } parseCalled = true; parse(); - // sets the index of each sequence in the alignment - for (int i = 0, c = seqs.size(); i < c; i++) - { - seqs.get(i).setIndex(i); - } } /** diff --git a/src/jalview/io/SequenceAnnotationReport.java b/src/jalview/io/SequenceAnnotationReport.java index 1f92428..6b82671 100644 --- a/src/jalview/io/SequenceAnnotationReport.java +++ b/src/jalview/io/SequenceAnnotationReport.java @@ -210,11 +210,12 @@ public class SequenceAnnotationReport FeatureColourI fc = fr.getFeatureColours().get(feature.getType()); if (fc != null && fc.isColourByAttribute()) { - String attName = fc.getAttributeName(); + String[] attName = fc.getAttributeName(); String attVal = feature.getValueAsString(attName); if (attVal != null) { - sb.append("; ").append(attName).append("=").append(attVal); + sb.append("; ").append(String.join(":", attName)).append("=") + .append(attVal); } } } @@ -301,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()) { diff --git a/src/jalview/io/cache/JvCacheableInputBox.java b/src/jalview/io/cache/JvCacheableInputBox.java index 3d0daed..a837512 100644 --- a/src/jalview/io/cache/JvCacheableInputBox.java +++ b/src/jalview/io/cache/JvCacheableInputBox.java @@ -28,6 +28,7 @@ import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -70,11 +71,49 @@ public class JvCacheableInputBox extends JComboBox private JMenuItem menuItemClearCache = new JMenuItem(); + volatile boolean enterWasPressed = false; + + /** + * @return flag indicating if the most recent keypress was enter + */ + public boolean wasEnterPressed() + { + return enterWasPressed; + } + public JvCacheableInputBox(String newCacheKey) { super(); this.cacheKey = newCacheKey; setEditable(true); + addKeyListener(new KeyListener() + { + + @Override + public void keyTyped(KeyEvent e) + { + enterWasPressed = false; + if (e.getKeyCode() == KeyEvent.VK_ENTER) + { + enterWasPressed = true; + } + // let event bubble up + } + + @Override + public void keyReleased(KeyEvent e) + { + // TODO Auto-generated method stub + + } + + @Override + public void keyPressed(KeyEvent e) + { + // TODO Auto-generated method stub + + } + }); setPrototypeDisplayValue( "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); appCache = AppCache.getInstance(); diff --git a/src/jalview/io/vcf/VCFLoader.java b/src/jalview/io/vcf/VCFLoader.java index 5adc55c..2847bd7 100644 --- a/src/jalview/io/vcf/VCFLoader.java +++ b/src/jalview/io/vcf/VCFLoader.java @@ -1,17 +1,9 @@ package jalview.io.vcf; -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; - import jalview.analysis.AlignmentUtils; import jalview.analysis.Dna; import jalview.api.AlignViewControllerGuiI; +import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.GeneLociI; @@ -35,6 +27,17 @@ 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.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 @@ -45,6 +48,18 @@ import java.util.Map.Entry; public class VCFLoader { /* + * 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 = "AF,AC*"; + + 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 */ @@ -54,23 +69,16 @@ public class VCFLoader private static final String FEATURE_KEY = "Feature"; // Ensembl stable id /* - * what comes before column headings in CSQ Description field - */ - private static final String FORMAT = "Format: "; - - /* * 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 CSQ identifier) + * - we don't handle this case (require identifier to be CSQ) */ - private static final String CSQ = "CSQ"; + private static final String CSQ_FIELD = "CSQ"; /* - * separator for fields in consequence data + * separator for fields in consequence data is '|' */ - private static final String PIPE = "|"; - - private static final String PIPE_REGEX = "\\" + PIPE; + private static final String PIPE_REGEX = "\\|"; /* * key for Allele Frequency output by VEP @@ -126,6 +134,19 @@ public class VCFLoader */ 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 an alignment context * @@ -136,7 +157,7 @@ public class VCFLoader al = alignment; // map of species!chromosome!fromAssembly!toAssembly to {fromRange, toRange} - assemblyMappings = new HashMap>(); + assemblyMappings = new HashMap<>(); } /** @@ -193,7 +214,7 @@ public class VCFLoader /* * get offset of CSQ ALLELE_NUM and Feature if declared */ - locateCsqFields(); + parseCsqHeader(); VCFHeaderLine ref = header .getOtherHeaderLine(VCFHeader.REFERENCE_KEY); @@ -253,11 +274,15 @@ public class VCFLoader * Reads metadata (such as INFO field descriptions and datatypes) and saves * them for future reference * - * @param sourceId + * @param theSourceId */ - void saveMetadata(String sourceId) + void saveMetadata(String theSourceId) { - FeatureSource metadata = new FeatureSource(sourceId); + List vcfFieldPatterns = getFieldMatchers(VCF_FIELDS_PREF, + DEFAULT_VCF_FIELDS); + vcfFieldsOfInterest = new ArrayList<>(); + + FeatureSource metadata = new FeatureSource(theSourceId); for (VCFInfoHeaderLine info : header.getInfoHeaderLines()) { @@ -285,34 +310,65 @@ public class VCFLoader } metadata.setAttributeName(attributeId, desc); metadata.setAttributeType(attributeId, attType); + + if (isFieldWanted(attributeId, vcfFieldPatterns)) + { + vcfFieldsOfInterest.add(attributeId); + } } - FeatureSources.getInstance().addSource(sourceId, metadata); + FeatureSources.getInstance().addSource(theSourceId, metadata); } /** - * Records the position of selected fields defined in the CSQ INFO header (if - * there is one). CSQ fields are declared in the CSQ INFO Description e.g. + * 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 locateCsqFields() + protected void parseCsqHeader() { - VCFInfoHeaderLine csqInfo = header.getInfoHeaderLine(CSQ); + 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 formatPos = desc.indexOf(FORMAT); - if (formatPos == -1) - { - System.err.println("Parse error, failed to find " + FORMAT - + " in " + desc); - return; - } - desc = desc.substring(formatPos + FORMAT.length()); + int spacePos = desc.lastIndexOf(" "); + desc = desc.substring(spacePos + 1); if (desc != null) { @@ -332,12 +388,51 @@ public class VCFLoader { 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. * @@ -702,13 +797,21 @@ public class VCFLoader * extract Consequence data (if present) that we are able to * associated with the allele for this variant feature */ - if (CSQ.equals(key)) + if (CSQ_FIELD.equals(key)) { addConsequences(variant, seq, sf, altAlelleIndex); 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') @@ -775,7 +878,7 @@ public class VCFLoader protected void addConsequences(VariantContext variant, SequenceI seq, SequenceFeature sf, int altAlelleIndex) { - Object value = variant.getAttribute(CSQ); + Object value = variant.getAttribute(CSQ_FIELD); if (value == null || !(value instanceof ArrayList)) { @@ -808,8 +911,11 @@ public class VCFLoader } } - StringBuilder sb = new StringBuilder(128); - boolean found = false; + /* + * inspect CSQ consequences; where possible restrict to the consequence + * associated with the current transcript (Feature) + */ + Map csqValues = new HashMap<>(); for (String consequence : consequences) { @@ -818,18 +924,29 @@ public class VCFLoader if (includeConsequence(csqFields, matchFeature, variant, altAlelleIndex)) { - if (found) + /* + * inspect individual fields of this consequence, copying non-null + * values which are 'fields of interest' + */ + int i = 0; + for (String field : csqFields) { - sb.append(COMMA); + if (field != null && field.length() > 0) + { + String id = vepFieldsOfInterest.get(i); + if (id != null) + { + csqValues.put(id, field); + } + } + i++; } - found = true; - sb.append(consequence); } } - if (found) + if (!csqValues.isEmpty()) { - sf.setValue(CSQ, sb.toString()); + sf.setValue(CSQ_FIELD, csqValues); } } diff --git a/src/jalview/schemes/FeatureColour.java b/src/jalview/schemes/FeatureColour.java index 168ab54..71a89b0 100644 --- a/src/jalview/schemes/FeatureColour.java +++ b/src/jalview/schemes/FeatureColour.java @@ -78,10 +78,10 @@ public class FeatureColour implements FeatureColourI private boolean colourByLabel; /* - * if not null, the value of this named attribute is used for - * colourByLabel or graduatedColour + * if not null, the value of [attribute, [sub-attribute] ...] + * is used for colourByLabel or graduatedColour */ - private String byAttributeName; + private String[] attributeName; private float threshold; @@ -371,7 +371,7 @@ public class FeatureColour implements FeatureColourI base = fc.base; range = fc.range; isHighToLow = fc.isHighToLow; - byAttributeName = fc.byAttributeName; + attributeName = fc.attributeName; setAboveThreshold(fc.isAboveThreshold()); setBelowThreshold(fc.isBelowThreshold()); setThreshold(fc.getThreshold()); @@ -593,8 +593,8 @@ public class FeatureColour implements FeatureColourI { if (isColourByLabel()) { - String label = byAttributeName == null ? feature.getDescription() - : feature.getValueAsString(byAttributeName); + String label = attributeName == null ? feature.getDescription() + : feature.getValueAsString(attributeName); return label == null ? noColour : ColorUtils .createColourFromName(label); } @@ -611,11 +611,11 @@ public class FeatureColour implements FeatureColourI * no such attribute is assigned the 'no value' colour */ float scr = feature.getScore(); - if (byAttributeName != null) + if (attributeName != null) { try { - String attVal = feature.getValueAsString(byAttributeName); + String attVal = feature.getValueAsString(attributeName); scr = Float.valueOf(attVal); } catch (Throwable e) { @@ -746,19 +746,19 @@ public class FeatureColour implements FeatureColourI @Override public boolean isColourByAttribute() { - return byAttributeName != null; + return attributeName != null; } @Override - public String getAttributeName() + public String[] getAttributeName() { - return byAttributeName; + return attributeName; } @Override - public void setAttributeName(String name) + public void setAttributeName(String... name) { - byAttributeName = name; + attributeName = name; } } diff --git a/src/jalview/util/matcher/KeyedMatcher.java b/src/jalview/util/matcher/KeyedMatcher.java index cd952e7..ef1c702 100644 --- a/src/jalview/util/matcher/KeyedMatcher.java +++ b/src/jalview/util/matcher/KeyedMatcher.java @@ -19,18 +19,20 @@ import java.util.function.Function; */ public class KeyedMatcher implements KeyedMatcherI { - final private String key; + private static final String COLON = ":"; + + final private String[] key; final private MatcherI matcher; /** * Constructor given a key, a test condition and a match pattern * - * @param theKey * @param cond * @param pattern + * @param theKey */ - public KeyedMatcher(String theKey, Condition cond, String pattern) + public KeyedMatcher(Condition cond, String pattern, String... theKey) { key = theKey; matcher = new Matcher(cond, pattern); @@ -41,25 +43,25 @@ public class KeyedMatcher implements KeyedMatcherI * to. Note that if a non-numerical condition is specified, the float will be * converted to a string. * - * @param theKey * @param cond * @param value + * @param theKey */ - public KeyedMatcher(String theKey, Condition cond, float value) + public KeyedMatcher(Condition cond, float value, String... theKey) { key = theKey; matcher = new Matcher(cond, value); } @Override - public boolean matches(Function valueProvider) + public boolean matches(Function valueProvider) { String value = valueProvider.apply(key); return matcher.matches(value); } @Override - public String getKey() + public String[] getKey() { return key; } @@ -78,8 +80,8 @@ public class KeyedMatcher implements KeyedMatcherI public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(key).append(" ").append(matcher.getCondition().toString()) - .append(" "); + sb.append(String.join(COLON, key)).append(" ") + .append(matcher.getCondition().toString()).append(" "); if (matcher.getCondition().isNumeric()) { sb.append(matcher.getPattern()); diff --git a/src/jalview/util/matcher/KeyedMatcherI.java b/src/jalview/util/matcher/KeyedMatcherI.java index e9fe014..e8d71c1 100644 --- a/src/jalview/util/matcher/KeyedMatcherI.java +++ b/src/jalview/util/matcher/KeyedMatcherI.java @@ -18,14 +18,14 @@ public interface KeyedMatcherI * @param valueProvider * @return */ - boolean matches(Function valueProvider); + boolean matches(Function valueProvider); /** * Answers the value key this matcher operates on * * @return */ - String getKey(); + String[] getKey(); /** * Answers the match condition that is applied diff --git a/src/jalview/util/matcher/KeyedMatcherSet.java b/src/jalview/util/matcher/KeyedMatcherSet.java index 35a41c2..a4be48a 100644 --- a/src/jalview/util/matcher/KeyedMatcherSet.java +++ b/src/jalview/util/matcher/KeyedMatcherSet.java @@ -19,7 +19,7 @@ public class KeyedMatcherSet implements KeyedMatcherSetI } @Override - public boolean matches(Function valueProvider) + public boolean matches(Function valueProvider) { /* * no conditions matches anything diff --git a/src/jalview/util/matcher/KeyedMatcherSetI.java b/src/jalview/util/matcher/KeyedMatcherSetI.java index 25dc96e..3e9f5b6 100644 --- a/src/jalview/util/matcher/KeyedMatcherSetI.java +++ b/src/jalview/util/matcher/KeyedMatcherSetI.java @@ -18,7 +18,7 @@ public interface KeyedMatcherSetI * @param valueProvider * @return */ - boolean matches(Function valueProvider); + boolean matches(Function valueProvider); /** * Answers a new object that matches the logical AND of this and m diff --git a/src/jalview/util/matcher/Matcher.java b/src/jalview/util/matcher/Matcher.java index a213a17..715694c 100644 --- a/src/jalview/util/matcher/Matcher.java +++ b/src/jalview/util/matcher/Matcher.java @@ -37,8 +37,8 @@ public class Matcher implements MatcherI * @param compareTo * @return * @throws NumberFormatException - * if a numerical condition is specified with a non-numeric - * comparision value + * if a numerical condition is specified with a non-numeric comparison + * value * @throws NullPointerException * if a null condition or comparison string is specified */ diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index c2f5bb7..28fceec 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -169,7 +169,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)) @@ -205,7 +205,7 @@ public abstract class FeatureRendererModel renderOrder = neworder; } - protected Map minmax = new Hashtable(); + protected Map minmax = new Hashtable<>(); public Map getMinMax() { @@ -284,7 +284,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; @@ -333,7 +333,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++) @@ -346,7 +346,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++) { @@ -426,7 +426,7 @@ public abstract class FeatureRendererModel */ if (minmax == null) { - minmax = new Hashtable(); + minmax = new Hashtable<>(); } synchronized (minmax) { @@ -463,7 +463,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); @@ -624,7 +624,7 @@ public abstract class FeatureRendererModel { if (featureOrder == null) { - featureOrder = new Hashtable(); + featureOrder = new Hashtable<>(); } featureOrder.put(type, new Float(position)); return position; @@ -683,7 +683,7 @@ public abstract class FeatureRendererModel * note visible feature ordering and colours before update */ List visibleFeatures = getDisplayedFeatureTypes(); - Map visibleColours = new HashMap( + Map visibleColours = new HashMap<>( getFeatureColours()); FeaturesDisplayedI av_featuresdisplayed = null; @@ -843,7 +843,7 @@ public abstract class FeatureRendererModel { if (featureGroups != null) { - List gp = new ArrayList(); + List gp = new ArrayList<>(); for (String grp : featureGroups.keySet()) { @@ -889,7 +889,7 @@ public abstract class FeatureRendererModel @Override public Map getDisplayedFeatureCols() { - Map fcols = new Hashtable(); + Map fcols = new Hashtable<>(); if (getViewport().getFeaturesDisplayed() == null) { return fcols; @@ -917,7 +917,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) { @@ -938,7 +938,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)) @@ -973,7 +973,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; diff --git a/test/jalview/datamodel/features/FeatureAttributesTest.java b/test/jalview/datamodel/features/FeatureAttributesTest.java new file mode 100644 index 0000000..e464326 --- /dev/null +++ b/test/jalview/datamodel/features/FeatureAttributesTest.java @@ -0,0 +1,41 @@ +package jalview.datamodel.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Comparator; + +import junit.extensions.PA; + +import org.testng.annotations.Test; + +public class FeatureAttributesTest +{ + + /** + * 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); + } +} diff --git a/test/jalview/gui/AlignViewportTest.java b/test/jalview/gui/AlignViewportTest.java index 812fd8f..5ed0cac 100644 --- a/test/jalview/gui/AlignViewportTest.java +++ b/test/jalview/gui/AlignViewportTest.java @@ -96,57 +96,6 @@ public class AlignViewportTest testee = new AlignViewport(al); } - @Test(groups = { "Functional" }) - public void testCollateForPdb() - { - // JBP: What behaviour is this supposed to test ? - /* - * Set up sequence pdb ids - */ - PDBEntry pdb1 = new PDBEntry("1ABC", "B", Type.PDB, "1ABC.pdb"); - PDBEntry pdb2 = new PDBEntry("2ABC", "C", Type.PDB, "2ABC.pdb"); - PDBEntry pdb3 = new PDBEntry("3ABC", "D", Type.PDB, "3ABC.pdb"); - - /* - * seq1 and seq3 refer to 1abcB, seq2 to 2abcC, none to 3abcD - */ - al.getSequenceAt(0).getDatasetSequence() - .addPDBId(new PDBEntry("1ABC", "B", Type.PDB, "1ABC.pdb")); - al.getSequenceAt(2).getDatasetSequence() - .addPDBId(new PDBEntry("1ABC", "B", Type.PDB, "1ABC.pdb")); - al.getSequenceAt(1).getDatasetSequence() - .addPDBId(new PDBEntry("2ABC", "C", Type.PDB, "2ABC.pdb")); - /* - * Add a second chain PDB xref to Seq2 - should not result in a duplicate in - * the results - */ - al.getSequenceAt(1).getDatasetSequence() - .addPDBId(new PDBEntry("2ABC", "D", Type.PDB, "2ABC.pdb")); - /* - * Seq3 refers to 3abc - this does not match 3ABC (as the code stands) - */ - al.getSequenceAt(2).getDatasetSequence() - .addPDBId(new PDBEntry("3abc", "D", Type.PDB, "3ABC.pdb")); - - /* - * run method under test - */ - SequenceI[][] seqs = testee.collateForPDB(new PDBEntry[] { pdb1, pdb2, - pdb3 }); - - // seq1 and seq3 refer to PDBEntry[0] - assertEquals(2, seqs[0].length); - assertSame(al.getSequenceAt(0), seqs[0][0]); - assertSame(al.getSequenceAt(2), seqs[0][1]); - - // seq2 refers to PDBEntry[1] - assertEquals(1, seqs[1].length); - assertSame(al.getSequenceAt(1), seqs[1][0]); - - // no sequence refers to PDBEntry[2] - assertEquals(0, seqs[2].length); - } - /** * Test that a mapping is not deregistered when a second view is closed but * the first still holds a reference to the mapping diff --git a/test/jalview/gui/StructureViewerTest.java b/test/jalview/gui/StructureViewerTest.java index c1c1d5c..4d5b114 100644 --- a/test/jalview/gui/StructureViewerTest.java +++ b/test/jalview/gui/StructureViewerTest.java @@ -1,10 +1,17 @@ package jalview.gui; 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.PDBEntry; import jalview.datamodel.PDBEntry.Type; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; + +import java.util.Map; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -20,9 +27,11 @@ public class StructureViewerTest } @Test(groups = "Functional") - public void testGetUniquePdbFiles() + public void testGetSequencesForPdbs() { - assertNull(StructureViewer.getUniquePdbFiles(null)); + StructureViewer sv = new StructureViewer(null); + + assertNull(sv.getSequencesForPdbs(null, null)); PDBEntry pdbe1 = new PDBEntry("1A70", "A", Type.PDB, "path1"); PDBEntry pdbe2 = new PDBEntry("3A6S", "A", Type.PDB, "path2"); @@ -30,13 +39,45 @@ public class StructureViewerTest PDBEntry pdbe4 = new PDBEntry("1GAQ", "A", Type.PDB, null); PDBEntry pdbe5 = new PDBEntry("3A6S", "B", Type.PDB, "path2"); PDBEntry pdbe6 = new PDBEntry("1GAQ", "B", Type.PDB, null); + PDBEntry[] pdbs = new PDBEntry[] { pdbe1, pdbe2, pdbe3, pdbe4, pdbe5, + pdbe6 }; + + /* + * seq1 ... seq6 associated with pdbe1 ... pdbe6 + */ + SequenceI[] seqs = new SequenceI[pdbs.length]; + for (int i = 0; i < seqs.length; i++) + { + seqs[i] = new Sequence("Seq" + i, "abc"); + } /* - * pdbe2 and pdbe5 get removed as having a duplicate file path + * pdbe3/5/6 should get removed as having a duplicate file path */ - PDBEntry[] uniques = StructureViewer.getUniquePdbFiles(new PDBEntry[] { - pdbe1, pdbe2, pdbe3, pdbe4, pdbe5, pdbe6 }); - assertEquals(uniques, - new PDBEntry[] { pdbe1, pdbe2, pdbe4, pdbe6 }); + Map uniques = sv.getSequencesForPdbs(pdbs, seqs); + assertTrue(uniques.containsKey(pdbe1)); + assertTrue(uniques.containsKey(pdbe2)); + assertFalse(uniques.containsKey(pdbe3)); + assertTrue(uniques.containsKey(pdbe4)); + assertFalse(uniques.containsKey(pdbe5)); + assertFalse(uniques.containsKey(pdbe6)); + + // 1A70 associates with seq1 and seq3 + SequenceI[] ss = uniques.get(pdbe1); + assertEquals(ss.length, 2); + assertSame(seqs[0], ss[0]); + assertSame(seqs[2], ss[1]); + + // 3A6S has seq2 and seq5 + ss = uniques.get(pdbe2); + assertEquals(ss.length, 2); + assertSame(seqs[1], ss[0]); + assertSame(seqs[4], ss[1]); + + // 1GAQ has seq4 and seq6 + ss = uniques.get(pdbe4); + assertEquals(ss.length, 2); + assertSame(seqs[3], ss[0]); + assertSame(seqs[5], ss[1]); } } 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 * *

      */ - @Test + @Test(groups = "Functional") public void testFindColour() { ColourSchemeI blosum = new Blosum62ColourScheme(); diff --git a/test/jalview/util/MapListTest.java b/test/jalview/util/MapListTest.java index d2db258..3fc6fe0 100644 --- a/test/jalview/util/MapListTest.java +++ b/test/jalview/util/MapListTest.java @@ -818,7 +818,7 @@ public class MapListTest /** * Test the method that compounds ('traverses') two mappings */ - @Test + @Test(groups = "Functional") public void testTraverse() { /* diff --git a/test/jalview/util/MathUtilsTest.java b/test/jalview/util/MathUtilsTest.java index fc84741..dc23472 100644 --- a/test/jalview/util/MathUtilsTest.java +++ b/test/jalview/util/MathUtilsTest.java @@ -6,7 +6,7 @@ import org.testng.annotations.Test; public class MathUtilsTest { - @Test + @Test(groups = "Functional") public void testGcd() { assertEquals(MathUtils.gcd(0, 0), 0); diff --git a/test/jalview/util/matcher/ConditionTest.java b/test/jalview/util/matcher/ConditionTest.java index 270aa2a..9d8b225 100644 --- a/test/jalview/util/matcher/ConditionTest.java +++ b/test/jalview/util/matcher/ConditionTest.java @@ -8,7 +8,7 @@ import org.testng.annotations.Test; public class ConditionTest { - @Test + @Test(groups = "Functional") public void testToString() { Locale.setDefault(Locale.UK); diff --git a/test/jalview/util/matcher/KeyedMatcherSetTest.java b/test/jalview/util/matcher/KeyedMatcherSetTest.java index 3018cb6..3d597d2 100644 --- a/test/jalview/util/matcher/KeyedMatcherSetTest.java +++ b/test/jalview/util/matcher/KeyedMatcherSetTest.java @@ -12,13 +12,13 @@ import org.testng.annotations.Test; public class KeyedMatcherSetTest { - @Test + @Test(groups = "Functional") public void testMatches() { /* * a numeric matcher - MatcherTest covers more conditions */ - KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); + KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF"); KeyedMatcherSetI kms = new KeyedMatcherSet(); kms.and(km); assertTrue(kms.matches(key -> "-2")); @@ -31,24 +31,25 @@ public class KeyedMatcherSetTest /* * a string pattern matcher */ - km = new KeyedMatcher("AF", Condition.Contains, "Cat"); + km = new KeyedMatcher(Condition.Contains, "Cat", "AF"); kms = new KeyedMatcherSet(); kms.and(km); assertTrue(kms - .matches(key -> "AF".equals(key) ? "raining cats and dogs" + .matches(key -> "AF".equals(key[0]) ? "raining cats and dogs" : "showers")); } - @Test + @Test(groups = "Functional") public void testAnd() { // condition1: AF value contains "dog" (matches) - KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog"); + KeyedMatcherI km1 = new KeyedMatcher(Condition.Contains, "dog", "AF"); // condition 2: CSQ value does not contain "how" (does not match) - KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains, - "how"); + KeyedMatcherI km2 = new KeyedMatcher(Condition.NotContains, "how", + "CSQ"); - Function vp = key -> "AF".equals(key) ? "raining cats and dogs" + Function vp = key -> "AF".equals(key[0]) + ? "raining cats and dogs" : "showers"; assertTrue(km1.matches(vp)); assertFalse(km2.matches(vp)); @@ -61,13 +62,14 @@ public class KeyedMatcherSetTest assertFalse(kms.matches(vp)); } - @Test + @Test(groups = "Functional") public void testToString() { - KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.LT, 1.2f); + KeyedMatcherI km1 = new KeyedMatcher(Condition.LT, 1.2f, "AF"); assertEquals(km1.toString(), "AF < 1.2"); - KeyedMatcher km2 = new KeyedMatcher("CLIN_SIG", Condition.NotContains, "path"); + KeyedMatcher km2 = new KeyedMatcher(Condition.NotContains, "path", + "CLIN_SIG"); assertEquals(km2.toString(), "CLIN_SIG Does not contain 'PATH'"); /* @@ -93,16 +95,17 @@ public class KeyedMatcherSetTest "(AF < 1.2) OR (CLIN_SIG Does not contain 'PATH')"); } - @Test + @Test(groups = "Functional") public void testOr() { // condition1: AF value contains "dog" (matches) - KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog"); + KeyedMatcherI km1 = new KeyedMatcher(Condition.Contains, "dog", "AF"); // condition 2: CSQ value does not contain "how" (does not match) - KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains, - "how"); + KeyedMatcherI km2 = new KeyedMatcher(Condition.NotContains, "how", + "CSQ"); - Function vp = key -> "AF".equals(key) ? "raining cats and dogs" + Function vp = key -> "AF".equals(key[0]) + ? "raining cats and dogs" : "showers"; assertTrue(km1.matches(vp)); assertFalse(km2.matches(vp)); @@ -114,17 +117,17 @@ public class KeyedMatcherSetTest assertTrue(kms.matches(vp)); } - @Test + @Test(groups = "Functional") public void testIsEmpty() { - KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); + KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF"); KeyedMatcherSetI kms = new KeyedMatcherSet(); assertTrue(kms.isEmpty()); kms.and(km); assertFalse(kms.isEmpty()); } - @Test + @Test(groups = "Functional") public void testGetMatchers() { KeyedMatcherSetI kms = new KeyedMatcherSet(); @@ -138,7 +141,7 @@ public class KeyedMatcherSetTest /* * one matcher: */ - KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.GE, -2F); + KeyedMatcherI km1 = new KeyedMatcher(Condition.GE, -2F, "AF"); kms.and(km1); iterator = kms.getMatchers().iterator(); assertSame(km1, iterator.next()); @@ -147,11 +150,44 @@ public class KeyedMatcherSetTest /* * two matchers: */ - KeyedMatcherI km2 = new KeyedMatcher("AF", Condition.LT, 8F); + KeyedMatcherI km2 = new KeyedMatcher(Condition.LT, 8F, "AF"); kms.and(km2); iterator = kms.getMatchers().iterator(); assertSame(km1, iterator.next()); assertSame(km2, 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 + */ + KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "CSQ", + "Consequence"); + KeyedMatcherSetI kms = new KeyedMatcherSet(); + kms.and(km); + assertTrue(kms.matches(key -> "-2")); + assertTrue(kms.matches(key -> "-1")); + assertFalse(kms.matches(key -> "-3")); + assertFalse(kms.matches(key -> "")); + assertFalse(kms.matches(key -> "junk")); + assertFalse(kms.matches(key -> null)); + + /* + * a string pattern matcher + */ + km = new KeyedMatcher(Condition.Contains, "Cat", "CSQ", "Consequence"); + kms = new KeyedMatcherSet(); + kms.and(km); + assertTrue(kms.matches(key -> "csq".equalsIgnoreCase(key[0]) + && "Consequence".equalsIgnoreCase(key[1]) + ? "raining cats and dogs" + : "showers")); + } } diff --git a/test/jalview/util/matcher/KeyedMatcherTest.java b/test/jalview/util/matcher/KeyedMatcherTest.java index 164b8eb..01d0067 100644 --- a/test/jalview/util/matcher/KeyedMatcherTest.java +++ b/test/jalview/util/matcher/KeyedMatcherTest.java @@ -14,7 +14,7 @@ public class KeyedMatcherTest /* * a numeric matcher - MatcherTest covers more conditions */ - KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); + KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF"); assertTrue(km.matches(key -> "-2")); assertTrue(km.matches(key -> "-1")); assertFalse(km.matches(key -> "-3")); @@ -25,9 +25,10 @@ public class KeyedMatcherTest /* * a string pattern matcher */ - km = new KeyedMatcher("AF", Condition.Contains, "Cat"); - assertTrue(km.matches(key -> "AF".equals(key) ? "raining cats and dogs" - : "showers")); + km = new KeyedMatcher(Condition.Contains, "Cat", "AF"); + assertTrue( + km.matches(key -> "AF".equals(key[0]) ? "raining cats and dogs" + : "showers")); } @Test @@ -36,21 +37,27 @@ public class KeyedMatcherTest /* * toString uses the i18n translation of the enum conditions */ - KeyedMatcherI km = new KeyedMatcher("AF", Condition.LT, 1.2f); + KeyedMatcherI km = new KeyedMatcher(Condition.LT, 1.2f, "AF"); assertEquals(km.toString(), "AF < 1.2"); } @Test public void testGetKey() { - KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); - assertEquals(km.getKey(), "AF"); + KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF"); + assertEquals(km.getKey(), new String[] { "AF" }); + + /* + * compound key (attribute / subattribute) + */ + km = new KeyedMatcher(Condition.GE, -2F, "CSQ", "Consequence"); + assertEquals(km.getKey(), new String[] { "CSQ", "Consequence" }); } @Test public void testGetMatcher() { - KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); + KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF"); assertEquals(km.getMatcher().getCondition(), Condition.GE); assertEquals(km.getMatcher().getFloatValue(), -2F); assertEquals(km.getMatcher().getPattern(), "-2.0"); diff --git a/test/jalview/util/matcher/MatcherTest.java b/test/jalview/util/matcher/MatcherTest.java index 489cdce..ee0ff82 100644 --- a/test/jalview/util/matcher/MatcherTest.java +++ b/test/jalview/util/matcher/MatcherTest.java @@ -10,7 +10,7 @@ import org.testng.annotations.Test; public class MatcherTest { - @Test + @Test(groups = "Functional") public void testConstructor() { MatcherI m = new Matcher(Condition.Contains, "foo"); @@ -55,7 +55,7 @@ public class MatcherTest /** * Tests for float comparison conditions */ - @Test + @Test(groups = "Functional") public void testMatches_float() { /* @@ -107,7 +107,7 @@ public class MatcherTest assertTrue(m.matches("1.9")); } - @Test + @Test(groups = "Functional") public void testMatches_floatNullOrInvalid() { for (Condition cond : Condition.values()) @@ -125,7 +125,7 @@ public class MatcherTest /** * Tests for string comparison conditions */ - @Test + @Test(groups = "Functional") public void testMatches_pattern() { /* @@ -177,7 +177,7 @@ public class MatcherTest /** * If a float is passed with a string condition it gets converted to a string */ - @Test + @Test(groups = "Functional") public void testMatches_floatWithStringCondition() { MatcherI m = new Matcher(Condition.Contains, 1.2e-6f); @@ -189,7 +189,7 @@ public class MatcherTest assertFalse(m.matches("0.0000001f")); } - @Test + @Test(groups = "Functional") public void testToString() { MatcherI m = new Matcher(Condition.LT, 1.2e-6f); @@ -202,7 +202,7 @@ public class MatcherTest assertEquals(m.toString(), "Contains '-1.2'"); } - @Test + @Test(groups = "Functional") public void testEquals() { /* @@ -235,7 +235,7 @@ public class MatcherTest assertFalse(m.equals(new Matcher(Condition.LT, -1.1f))); } - @Test + @Test(groups = "Functional") public void testHashCode() { MatcherI m1 = new Matcher(Condition.NotMatches, "ABC");