From: Jim Procter Date: Thu, 26 Oct 2017 10:51:35 +0000 (+0100) Subject: Merge branch 'bug/JAL-2776toggleGroupColour' into develop X-Git-Tag: Release_2_10_3b1~62 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=bdc95b0e4b04db5c121646c45b920506c66dc2a1;hp=cbbb5ab209dac86be8439435c9a4fc58371b5429;p=jalview.git Merge branch 'bug/JAL-2776toggleGroupColour' into develop --- diff --git a/help/html/calculations/pairwise.html b/help/html/calculations/pairwise.html index bb80b84..1090253 100755 --- a/help/html/calculations/pairwise.html +++ b/help/html/calculations/pairwise.html @@ -38,11 +38,21 @@

Gap open : 12
Gap extend : 2

-

When you select the pairwise alignment option a new window will - come up which will display the alignments in a text format as they - are calculated. Also displayed is information about the alignment - such as alignment score, length and percentage identity between the +

When you select the pairwise alignment option, a new window + will come up which displays the alignments in a text format, for + example:

+

+

+    FER1_SPIOL/5-13 TTMMGMAT
+ |. .. ||
+ FER1_MESCR/5-15 TAALSGAT +
+ shows the aligned sequences, where '|' links identical residues, and + (for peptide) '.' links residues that have a positive PAM250 score. +

The window also shows information about the alignment such as + alignment score, length and percentage identity between the sequences.

-

 

+

A button is also provided to allow you to view the sequences as + an alignment.

diff --git a/help/html/releases.html b/help/html/releases.html index 9cfc4f4..479083d 100755 --- a/help/html/releases.html +++ b/help/html/releases.html @@ -89,18 +89,51 @@ li:before { Structure views don't get updated unless their colours have changed +
  • All linked sequences are highlighted for a structure mousover (Jmol) or selection (Chimera)
  • +
  • 'Cancel' button in progress bar for JABAWS AACon, RNAAliFold and Disorder prediction jobs +
  • + +
  • Stop codons are excluded in CDS/Protein view from Ensembl locus cross-references
  • +
  • Start/End limits are shown in Pairwise Alignment report
  • + Testing and Deployment + +
    - + General + + Desktop + Applet
    + +
    @@ -1458,6 +1491,10 @@ li:before { after clicking on it to create new annotation for a column. +
  • + Null Pointer Exception raised when + pressing Add on an orphaned cut'n'paste window. +
  • diff --git a/src/MCview/AppletPDBCanvas.java b/src/MCview/AppletPDBCanvas.java index f94faba..b15c3cc 100644 --- a/src/MCview/AppletPDBCanvas.java +++ b/src/MCview/AppletPDBCanvas.java @@ -159,7 +159,7 @@ public class AppletPDBCanvas extends Panel try { - pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol); + pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol, null); if (protocol == DataSourceType.PASTE) { diff --git a/src/MCview/PDBCanvas.java b/src/MCview/PDBCanvas.java index b2f2503..ab172f2 100644 --- a/src/MCview/PDBCanvas.java +++ b/src/MCview/PDBCanvas.java @@ -153,7 +153,8 @@ public class PDBCanvas extends JPanel try { - pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol); + pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol, + ap.alignFrame); if (protocol.equals(jalview.io.DataSourceType.PASTE)) { diff --git a/src/jalview/analysis/AlignSeq.java b/src/jalview/analysis/AlignSeq.java index 34a21e6..1b2578e 100755 --- a/src/jalview/analysis/AlignSeq.java +++ b/src/jalview/analysis/AlignSeq.java @@ -36,6 +36,7 @@ import jalview.util.MessageManager; import java.awt.Color; import java.awt.Graphics; +import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -49,6 +50,14 @@ import java.util.StringTokenizer; */ public class AlignSeq { + private static final int MAX_NAME_LENGTH = 30; + + private static final int GAP_OPEN_COST = 120; + + private static final int GAP_EXTEND_COST = 20; + + private static final int GAP_INDEX = -1; + public static final String PEP = "pep"; public static final String DNA = "dna"; @@ -61,7 +70,7 @@ public class AlignSeq float[][] F; - int[][] traceback; + int[][] traceback; // todo is this actually used? int[] seq1; @@ -96,30 +105,20 @@ public class AlignSeq /** DOCUMENT ME!! */ public int seq2start; - /** DOCUMENT ME!! */ public int seq2end; int count; - /** DOCUMENT ME!! */ public float maxscore; - float pid; - int prev = 0; - int gapOpen = 120; - - int gapExtend = 20; - StringBuffer output = new StringBuffer(); String type; // AlignSeq.PEP or AlignSeq.DNA private ScoreMatrix scoreMatrix; - private static final int GAP_INDEX = -1; - /** * Creates a new AlignSeq object. * @@ -378,11 +377,10 @@ public class AlignSeq } } - // System.out.println(maxi + " " + maxj + " " + score[maxi][maxj]); int i = maxi; int j = maxj; int trace; - maxscore = score[i][j] / 10; + maxscore = score[i][j] / 10f; seq1end = maxi + 1; seq2end = maxj + 1; @@ -451,49 +449,48 @@ public class AlignSeq /** * DOCUMENT ME! */ - public void printAlignment(java.io.PrintStream os) + public void printAlignment(PrintStream os) { // TODO: Use original sequence characters rather than re-translated // characters in output // Find the biggest id length for formatting purposes - String s1id = s1.getName(), s2id = s2.getName(); - int maxid = s1.getName().length(); - if (s2.getName().length() > maxid) - { - maxid = s2.getName().length(); - } - if (maxid > 30) + String s1id = getAlignedSeq1().getDisplayId(true); + String s2id = getAlignedSeq2().getDisplayId(true); + int nameLength = Math.max(s1id.length(), s2id.length()); + if (nameLength > MAX_NAME_LENGTH) { - maxid = 30; + int truncateBy = nameLength - MAX_NAME_LENGTH; + nameLength = MAX_NAME_LENGTH; // JAL-527 - truncate the sequence ids - if (s1.getName().length() > maxid) + if (s1id.length() > nameLength) { - s1id = s1.getName().substring(0, 30); + int slashPos = s1id.lastIndexOf('/'); + s1id = s1id.substring(0, slashPos - truncateBy) + + s1id.substring(slashPos); } - if (s2.getName().length() > maxid) + if (s2id.length() > nameLength) { - s2id = s2.getName().substring(0, 30); + int slashPos = s2id.lastIndexOf('/'); + s2id = s2id.substring(0, slashPos - truncateBy) + + s2id.substring(slashPos); } } - int len = 72 - maxid - 1; + int len = 72 - nameLength - 1; int nochunks = ((aseq1.length - count) / len) + ((aseq1.length - count) % len > 0 ? 1 : 0); - pid = 0; + float pid = 0f; output.append("Score = ").append(score[maxi][maxj]).append(NEWLINE); output.append("Length of alignment = ") .append(String.valueOf(aseq1.length - count)).append(NEWLINE); output.append("Sequence "); - output.append(new Format("%" + maxid + "s").form(s1.getName())); - output.append(" : ").append(String.valueOf(s1.getStart())) - .append(" - ").append(String.valueOf(s1.getEnd())); + Format nameFormat = new Format("%" + nameLength + "s"); + output.append(nameFormat.form(s1id)); output.append(" (Sequence length = ") .append(String.valueOf(s1str.length())).append(")") .append(NEWLINE); output.append("Sequence "); - output.append(new Format("%" + maxid + "s").form(s2.getName())); - output.append(" : ").append(String.valueOf(s2.getStart())) - .append(" - ").append(String.valueOf(s2.getEnd())); + output.append(nameFormat.form(s2id)); output.append(" (Sequence length = ") .append(String.valueOf(s2str.length())).append(")") .append(NEWLINE).append(NEWLINE); @@ -503,7 +500,7 @@ public class AlignSeq for (int j = 0; j < nochunks; j++) { // Print the first aligned sequence - output.append(new Format("%" + (maxid) + "s").form(s1id)).append(" "); + output.append(nameFormat.form(s1id)).append(" "); for (int i = 0; i < len; i++) { @@ -514,7 +511,7 @@ public class AlignSeq } output.append(NEWLINE); - output.append(new Format("%" + (maxid) + "s").form(" ")).append(" "); + output.append(nameFormat.form(" ")).append(" "); /* * Print out the match symbols: @@ -534,7 +531,7 @@ public class AlignSeq pid++; output.append("|"); } - else if (type.equals("pep")) + else if (PEP.equals(type)) { if (pam250.getPairwiseScore(c1, c2) > 0) { @@ -554,8 +551,7 @@ public class AlignSeq // Now print the second aligned sequence output = output.append(NEWLINE); - output = output.append(new Format("%" + (maxid) + "s").form(s2id)) - .append(" "); + output = output.append(nameFormat.form(s2id)).append(" "); for (int i = 0; i < len; i++) { @@ -569,7 +565,8 @@ public class AlignSeq } pid = pid / (aseq1.length - count) * 100; - output = output.append(new Format("Percentage ID = %2.2f\n").form(pid)); + output.append(new Format("Percentage ID = %3.2f\n").form(pid)); + output.append(NEWLINE); try { os.print(output.toString()); @@ -591,7 +588,6 @@ public class AlignSeq public int findTrace(int i, int j) { int t = 0; - // float pairwiseScore = lookup[seq1[i]][seq2[j]]; float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i), s2str.charAt(j)); float max = score[i - 1][j - 1] + (pairwiseScore * 10); @@ -640,19 +636,19 @@ public class AlignSeq // top left hand element score[0][0] = scoreMatrix.getPairwiseScore(s1str.charAt(0), s2str.charAt(0)) * 10; - E[0][0] = -gapExtend; + E[0][0] = -GAP_EXTEND_COST; F[0][0] = 0; // Calculate the top row first for (int j = 1; j < m; j++) { // What should these values be? 0 maybe - E[0][j] = max(score[0][j - 1] - gapOpen, E[0][j - 1] - gapExtend); - F[0][j] = -gapExtend; + E[0][j] = max(score[0][j - 1] - GAP_OPEN_COST, E[0][j - 1] - GAP_EXTEND_COST); + F[0][j] = -GAP_EXTEND_COST; float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(0), s2str.charAt(j)); - score[0][j] = max(pairwiseScore * 10, -gapOpen, -gapExtend); + score[0][j] = max(pairwiseScore * 10, -GAP_OPEN_COST, -GAP_EXTEND_COST); traceback[0][j] = 1; } @@ -660,8 +656,8 @@ public class AlignSeq // Now do the left hand column for (int i = 1; i < n; i++) { - E[i][0] = -gapOpen; - F[i][0] = max(score[i - 1][0] - gapOpen, F[i - 1][0] - gapExtend); + E[i][0] = -GAP_OPEN_COST; + F[i][0] = max(score[i - 1][0] - GAP_OPEN_COST, F[i - 1][0] - GAP_EXTEND_COST); float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i), s2str.charAt(0)); @@ -674,8 +670,8 @@ public class AlignSeq { for (int j = 1; j < m; j++) { - E[i][j] = max(score[i][j - 1] - gapOpen, E[i][j - 1] - gapExtend); - F[i][j] = max(score[i - 1][j] - gapOpen, F[i - 1][j] - gapExtend); + E[i][j] = max(score[i][j - 1] - GAP_OPEN_COST, E[i][j - 1] - GAP_EXTEND_COST); + F[i][j] = max(score[i - 1][j] - GAP_OPEN_COST, F[i - 1][j] - GAP_EXTEND_COST); float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i), s2str.charAt(j)); diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index 2b9b9f9..90d9197 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -2164,7 +2164,10 @@ public class AlignmentUtils /** * Returns a mapping from dna to protein by inspecting sequence features of - * type "CDS" on the dna. + * type "CDS" on the dna. A mapping is constructed if the total CDS feature + * length is 3 times the peptide length (optionally after dropping a trailing + * stop codon). This method does not check whether the CDS nucleotide sequence + * translates to the peptide sequence. * * @param dnaSeq * @param proteinSeq @@ -2176,6 +2179,15 @@ public class AlignmentUtils List ranges = findCdsPositions(dnaSeq); int mappedDnaLength = MappingUtils.getLength(ranges); + /* + * if not a whole number of codons, something is wrong, + * abort mapping + */ + if (mappedDnaLength % CODON_LENGTH > 0) + { + return null; + } + int proteinLength = proteinSeq.getLength(); int proteinStart = proteinSeq.getStart(); int proteinEnd = proteinSeq.getEnd(); @@ -2199,8 +2211,12 @@ public class AlignmentUtils if (codesForResidues == (proteinLength + 1)) { // assuming extra codon is for STOP and not in peptide + // todo: check trailing codon is indeed a STOP codon codesForResidues--; + mappedDnaLength -= CODON_LENGTH; + MappingUtils.removeEndPositions(CODON_LENGTH, ranges); } + if (codesForResidues == proteinLength) { proteinRange.add(new int[] { proteinStart, proteinEnd }); @@ -2211,7 +2227,7 @@ public class AlignmentUtils /** * Returns a list of CDS ranges found (as sequence positions base 1), i.e. of - * start/end positions of sequence features of type "CDS" (or a sub-type of + * [start, end] positions of sequence features of type "CDS" (or a sub-type of * CDS in the Sequence Ontology). The ranges are sorted into ascending start * position order, so this method is only valid for linear CDS in the same * sense as the protein product. @@ -2230,7 +2246,6 @@ public class AlignmentUtils return result; } SequenceFeatures.sortFeatures(sfs, true); - int startPhase = 0; for (SequenceFeature sf : sfs) { @@ -2248,7 +2263,7 @@ public class AlignmentUtils */ int begin = sf.getBegin(); int end = sf.getEnd(); - if (result.isEmpty()) + if (result.isEmpty() && phase > 0) { begin += phase; if (begin > end) @@ -2263,16 +2278,6 @@ public class AlignmentUtils } /* - * remove 'startPhase' positions (usually 0) from the first range - * so we begin at the start of a complete codon - */ - if (!result.isEmpty()) - { - // TODO JAL-2022 correctly model start phase > 0 - result.get(0)[0] += startPhase; - } - - /* * Finally sort ranges by start position. This avoids a dependency on * keeping features in order on the sequence (if they are in order anyway, * the sort will have almost no work to do). The implicit assumption is CDS diff --git a/src/jalview/api/AlignViewportI.java b/src/jalview/api/AlignViewportI.java index 3cb06c1..931eba6 100644 --- a/src/jalview/api/AlignViewportI.java +++ b/src/jalview/api/AlignViewportI.java @@ -21,6 +21,7 @@ package jalview.api; import jalview.analysis.Conservation; +import jalview.analysis.TreeModel; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentView; @@ -485,4 +486,8 @@ public interface AlignViewportI extends ViewStyleI */ @Override void setProteinFontAsCdna(boolean b); + + public abstract TreeModel getCurrentTree(); + + public abstract void setCurrentTree(TreeModel tree); } diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index 676d3cf..ef87671 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -1603,10 +1603,12 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, { System.exit(0); } - else + + viewport = null; + if (alignPanel != null && alignPanel.overviewPanel != null) { + alignPanel.overviewPanel.dispose(); } - viewport = null; alignPanel = null; this.dispose(); } @@ -4147,7 +4149,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, { // register the association(s) and quit, don't create any windows. if (StructureSelectionManager.getStructureSelectionManager(applet) - .setMapping(seqs, chains, pdb.getFile(), protocol) == null) + .setMapping(seqs, chains, pdb.getFile(), protocol, null) == null) { System.err.println("Failed to map " + pdb.getFile() + " (" + protocol + ") to any sequences"); diff --git a/src/jalview/appletgui/AlignViewport.java b/src/jalview/appletgui/AlignViewport.java index b07666e..c83f93f 100644 --- a/src/jalview/appletgui/AlignViewport.java +++ b/src/jalview/appletgui/AlignViewport.java @@ -20,7 +20,6 @@ */ package jalview.appletgui; -import jalview.analysis.TreeModel; import jalview.api.AlignViewportI; import jalview.api.FeatureSettingsModelI; import jalview.bin.JalviewLite; @@ -54,8 +53,6 @@ public class AlignViewport extends AlignmentViewport boolean validCharWidth = true; - TreeModel currentTree = null; - public jalview.bin.JalviewLite applet; boolean MAC = false; @@ -274,16 +271,6 @@ public class AlignViewport extends AlignmentViewport ranges.setEndSeq(height / getCharHeight()); } - public void setCurrentTree(TreeModel tree) - { - currentTree = tree; - } - - public TreeModel getCurrentTree() - { - return currentTree; - } - boolean centreColumnLabels; public boolean getCentreColumnLabels() diff --git a/src/jalview/appletgui/AppletJmol.java b/src/jalview/appletgui/AppletJmol.java index 49219b9..3d1442d 100644 --- a/src/jalview/appletgui/AppletJmol.java +++ b/src/jalview/appletgui/AppletJmol.java @@ -134,7 +134,7 @@ public class AppletJmol extends EmbmenuFrame implements AlignmentPanel ap; - List _aps = new ArrayList(); // remove? never + List _aps = new ArrayList<>(); // remove? never // added to String fileLoadingError; @@ -213,7 +213,7 @@ public class AppletJmol extends EmbmenuFrame implements { reader = StructureSelectionManager .getStructureSelectionManager(ap.av.applet) - .setMapping(seq, chains, pdbentry.getFile(), protocol); + .setMapping(seq, chains, pdbentry.getFile(), protocol, null); // PROMPT USER HERE TO ADD TO NEW OR EXISTING VIEW? // FOR NOW, LETS JUST OPEN A NEW WINDOW } @@ -394,7 +394,7 @@ public class AppletJmol extends EmbmenuFrame implements void centerViewer() { - Vector toshow = new Vector(); + Vector toshow = new Vector<>(); for (int i = 0; i < chainMenu.getItemCount(); i++) { if (chainMenu.getItem(i) instanceof CheckboxMenuItem) diff --git a/src/jalview/appletgui/AppletJmolBinding.java b/src/jalview/appletgui/AppletJmolBinding.java index d5d53fb..2f61b24 100644 --- a/src/jalview/appletgui/AppletJmolBinding.java +++ b/src/jalview/appletgui/AppletJmolBinding.java @@ -24,6 +24,7 @@ import jalview.api.AlignmentViewPanel; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.ext.jmol.JalviewJmolBinding; +import jalview.gui.IProgressIndicator; import jalview.io.DataSourceType; import jalview.structure.StructureSelectionManager; @@ -183,4 +184,11 @@ class AppletJmolBinding extends JalviewJmolBinding // TODO Auto-generated method stub return null; } + + @Override + protected IProgressIndicator getIProgressIndicator() + { + // no progress indicators on the applet + return null; + } } diff --git a/src/jalview/appletgui/ExtJmol.java b/src/jalview/appletgui/ExtJmol.java index 3966536..89228d5 100644 --- a/src/jalview/appletgui/ExtJmol.java +++ b/src/jalview/appletgui/ExtJmol.java @@ -26,6 +26,7 @@ import jalview.api.SequenceRenderer; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; import jalview.ext.jmol.JalviewJmolBinding; +import jalview.gui.IProgressIndicator; import jalview.io.DataSourceType; import java.awt.Container; @@ -65,6 +66,13 @@ public class ExtJmol extends JalviewJmolBinding } @Override + protected IProgressIndicator getIProgressIndicator() + { + // no progress indicators on applet (could access javascript for this) + return null; + } + + @Override public void updateColours(Object source) { @@ -92,6 +100,7 @@ public class ExtJmol extends JalviewJmolBinding } } + @Override public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment) { @@ -137,8 +146,8 @@ public class ExtJmol extends JalviewJmolBinding @Override public void refreshPdbEntries() { - List pdbe = new ArrayList(); - List fileids = new ArrayList(); + List pdbe = new ArrayList<>(); + List fileids = new ArrayList<>(); SequenceI[] sq = ap.av.getAlignment().getSequencesArray(); for (int s = 0; s < sq.length; s++) { diff --git a/src/jalview/appletgui/OverviewPanel.java b/src/jalview/appletgui/OverviewPanel.java index 0256055..8ce597d 100755 --- a/src/jalview/appletgui/OverviewPanel.java +++ b/src/jalview/appletgui/OverviewPanel.java @@ -31,6 +31,7 @@ import java.awt.BorderLayout; import java.awt.CheckboxMenuItem; import java.awt.Cursor; import java.awt.Dimension; +import java.awt.Frame; import java.awt.Panel; import java.awt.PopupMenu; import java.awt.event.ComponentAdapter; @@ -322,6 +323,9 @@ public class OverviewPanel extends Panel implements Runnable, try { av.getRanges().removePropertyChangeListener(this); + Frame parent = (Frame) getParent(); + parent.dispose(); + parent.setVisible(false); } finally { av = null; diff --git a/src/jalview/appletgui/PaintRefresher.java b/src/jalview/appletgui/PaintRefresher.java index 32507fe..fe99187 100755 --- a/src/jalview/appletgui/PaintRefresher.java +++ b/src/jalview/appletgui/PaintRefresher.java @@ -24,8 +24,8 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import java.awt.Component; -import java.util.Enumeration; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; @@ -78,13 +78,14 @@ public class PaintRefresher return; } - for (String id : components.keySet()) + Iterator it = components.keySet().iterator(); + while (it.hasNext()) { - Vector comps = components.get(id); + Vector comps = components.get(it.next()); comps.removeElement(comp); - if (comps.size() == 0) + if (comps.isEmpty()) { - components.remove(id); + it.remove(); } } } @@ -110,10 +111,10 @@ public class PaintRefresher return; } - Enumeration e = comps.elements(); - while (e.hasMoreElements()) + Iterator it = comps.iterator(); + while (it.hasNext()) { - comp = e.nextElement(); + comp = it.next(); if (comp == source) { @@ -122,7 +123,7 @@ public class PaintRefresher if (!comp.isValid()) { - comps.removeElement(comp); + it.remove(); } else if (validateSequences && comp instanceof AlignmentPanel && source instanceof AlignmentPanel) diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index 2ee412b..96b0757 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -38,8 +38,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Vector; -import com.stevesoft.pat.Regex; - import fr.orsay.lri.varna.models.rna.RNA; /** @@ -51,11 +49,6 @@ import fr.orsay.lri.varna.models.rna.RNA; */ public class Sequence extends ASequence implements SequenceI { - private static final Regex limitrx = new Regex( - "[/][0-9]{1,}[-][0-9]{1,}$"); - - private static final Regex endrx = new Regex("[0-9]{1,}$"); - SequenceI datasetSequence; String name; @@ -151,6 +144,10 @@ public class Sequence extends ASequence implements SequenceI checkValidRange(); } + /** + * If 'name' ends in /i-j, where i >= j > 0 are integers, extracts i and j as + * start and end respectively and removes the suffix from the name + */ void parseId() { if (name == null) @@ -159,17 +156,37 @@ public class Sequence extends ASequence implements SequenceI "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor."); name = ""; } - // Does sequence have the /start-end signature? - if (limitrx.search(name)) + int slashPos = name.lastIndexOf('/'); + if (slashPos > -1 && slashPos < name.length() - 1) { - name = limitrx.left(); - endrx.search(limitrx.stringMatched()); - setStart(Integer.parseInt(limitrx.stringMatched().substring(1, - endrx.matchedFrom() - 1))); - setEnd(Integer.parseInt(endrx.stringMatched())); + String suffix = name.substring(slashPos + 1); + String[] range = suffix.split("-"); + if (range.length == 2) + { + try + { + int from = Integer.valueOf(range[0]); + int to = Integer.valueOf(range[1]); + if (from > 0 && to >= from) + { + name = name.substring(0, slashPos); + setStart(from); + setEnd(to); + checkValidRange(); + } + } catch (NumberFormatException e) + { + // leave name unchanged if suffix is invalid + } + } } } + /** + * Ensures that 'end' is not before the end of the sequence, that is, + * (end-start+1) is at least as long as the count of ungapped positions. Note + * that end is permitted to be beyond the end of the sequence data. + */ void checkValidRange() { // Note: JAL-774 : @@ -178,7 +195,7 @@ public class Sequence extends ASequence implements SequenceI int endRes = 0; for (int j = 0; j < sequence.length; j++) { - if (!jalview.util.Comparison.isGap(sequence[j])) + if (!Comparison.isGap(sequence[j])) { endRes++; } @@ -453,15 +470,15 @@ public class Sequence extends ASequence implements SequenceI } /** - * DOCUMENT ME! + * Sets the sequence name. If the name ends in /start-end, then the start-end + * values are parsed out and set, and the suffix is removed from the name. * - * @param name - * DOCUMENT ME! + * @param theName */ @Override - public void setName(String name) + public void setName(String theName) { - this.name = name; + this.name = theName; this.parseId(); } diff --git a/src/jalview/ext/ensembl/EnsemblGene.java b/src/jalview/ext/ensembl/EnsemblGene.java index ad01324..50dfa90 100644 --- a/src/jalview/ext/ensembl/EnsemblGene.java +++ b/src/jalview/ext/ensembl/EnsemblGene.java @@ -161,8 +161,9 @@ public class EnsemblGene extends EnsemblSeqProxy } /** - * Converts a query, which may contain one or more gene or transcript - * identifiers, into a non-redundant list of gene identifiers. + * Converts a query, which may contain one or more gene, transcript, or + * external (to Ensembl) identifiers, into a non-redundant list of gene + * identifiers. * * @param accessions * @return @@ -173,54 +174,30 @@ public class EnsemblGene extends EnsemblSeqProxy for (String acc : accessions.split(getAccessionSeparator())) { - if (isGeneIdentifier(acc)) - { - if (!geneIds.contains(acc)) - { - geneIds.add(acc); - } - } - /* - * if given a transcript id, look up its gene parent + * First try lookup as an Ensembl (gene or transcript) identifier */ - else if (isTranscriptIdentifier(acc)) + String geneId = new EnsemblLookup(getDomain()).getGeneId(acc); + if (geneId != null) { - String geneId = new EnsemblLookup(getDomain()).getParent(acc); - if (geneId != null && !geneIds.contains(geneId)) + if (!geneIds.contains(geneId)) { geneIds.add(geneId); } } - else if (isProteinIdentifier(acc)) - { - String tscriptId = new EnsemblLookup(getDomain()).getParent(acc); - if (tscriptId != null) - { - String geneId = new EnsemblLookup(getDomain()) - .getParent(tscriptId); - - if (geneId != null && !geneIds.contains(geneId)) - { - geneIds.add(geneId); - } - } - // NOTE - acc is lost if it resembles an ENS.+ ID but isn't actually - // resolving to one... e.g. ENSMICP00000009241 - } - /* - * if given a gene or other external name, lookup and fetch - * the corresponding gene for all model organisms - */ else { + /* + * if given a gene or other external name, lookup and fetch + * the corresponding gene for all model organisms + */ List ids = new EnsemblSymbol(getDomain(), getDbSource(), - getDbVersion()).getIds(acc); - for (String geneId : ids) + getDbVersion()).getGeneIds(acc); + for (String id : ids) { - if (!geneIds.contains(geneId)) + if (!geneIds.contains(id)) { - geneIds.add(geneId); + geneIds.add(id); } } } @@ -229,30 +206,6 @@ public class EnsemblGene extends EnsemblSeqProxy } /** - * Attempts to get Ensembl stable identifiers for model organisms for a gene - * name by calling the xrefs symbol REST service to resolve the gene name. - * - * @param query - * @return - */ - protected String getGeneIdentifiersForName(String query) - { - List ids = new EnsemblSymbol(getDomain(), getDbSource(), - getDbVersion()).getIds(query); - if (ids != null) - { - for (String id : ids) - { - if (isGeneIdentifier(id)) - { - return id; - } - } - } - return null; - } - - /** * Constructs all transcripts for the gene, as identified by "transcript" * features whose Parent is the requested gene. The coding transcript * sequences (i.e. with introns omitted) are added to the alignment. diff --git a/src/jalview/ext/ensembl/EnsemblGenomes.java b/src/jalview/ext/ensembl/EnsemblGenomes.java index ef46a5b..b40df50 100644 --- a/src/jalview/ext/ensembl/EnsemblGenomes.java +++ b/src/jalview/ext/ensembl/EnsemblGenomes.java @@ -39,17 +39,12 @@ public class EnsemblGenomes extends EnsemblGene } @Override - public boolean isGeneIdentifier(String query) - { - return true; - } - - @Override public String getDbName() { return "EnsemblGenomes"; } + private String Wrong[]; @Override public String getTestQuery() { diff --git a/src/jalview/ext/ensembl/EnsemblLookup.java b/src/jalview/ext/ensembl/EnsemblLookup.java index 6483401..31da9c0 100644 --- a/src/jalview/ext/ensembl/EnsemblLookup.java +++ b/src/jalview/ext/ensembl/EnsemblLookup.java @@ -43,6 +43,13 @@ import org.json.simple.parser.ParseException; public class EnsemblLookup extends EnsemblRestClient { + private static final String OBJECT_TYPE_TRANSLATION = "Translation"; + private static final String PARENT = "Parent"; + private static final String OBJECT_TYPE_TRANSCRIPT = "Transcript"; + private static final String ID = "id"; + private static final String OBJECT_TYPE_GENE = "Gene"; + private static final String OBJECT_TYPE = "object_type"; + /** * Default constructor (to use rest.ensembl.org) */ @@ -87,7 +94,7 @@ public class EnsemblLookup extends EnsemblRestClient protected URL getUrl(String identifier) { String url = getDomain() + "/lookup/id/" + identifier - + "?content-type=application/json"; + + CONTENT_TYPE_JSON; try { return new URL(url); @@ -122,7 +129,7 @@ public class EnsemblLookup extends EnsemblRestClient * @param identifier * @return */ - public String getParent(String identifier) + public String getGeneId(String identifier) { List ids = Arrays.asList(new String[] { identifier }); @@ -155,8 +162,10 @@ public class EnsemblLookup extends EnsemblRestClient } /** - * Parses "Parent" from the JSON response and returns the value, or null if - * not found + * Parses the JSON response and returns the gene identifier, or null if not + * found. If the returned object_type is Gene, returns the id, if Transcript + * returns the Parent. If it is Translation (peptide identifier), then the + * Parent is the transcript identifier, so we redo the search with this value. * * @param br * @return @@ -164,17 +173,42 @@ public class EnsemblLookup extends EnsemblRestClient */ protected String parseResponse(BufferedReader br) throws IOException { - String parent = null; + String geneId = null; JSONParser jp = new JSONParser(); try { JSONObject val = (JSONObject) jp.parse(br); - parent = val.get("Parent").toString(); + String type = val.get(OBJECT_TYPE).toString(); + if (OBJECT_TYPE_GENE.equalsIgnoreCase(type)) + { + geneId = val.get(ID).toString(); + } + else if (OBJECT_TYPE_TRANSCRIPT.equalsIgnoreCase(type)) + { + geneId = val.get(PARENT).toString(); + } + else if (OBJECT_TYPE_TRANSLATION.equalsIgnoreCase(type)) + { + String transcriptId = val.get(PARENT).toString(); + try + { + geneId = getGeneId(transcriptId); + } catch (StackOverflowError e) + { + /* + * unlikely data condition error! + */ + System.err + .println("** Ensembl lookup " + + getUrl(transcriptId).toString() + + " looping on Parent!"); + } + } } catch (ParseException e) { // ignore } - return parent; + return geneId; } } diff --git a/src/jalview/ext/ensembl/EnsemblProtein.java b/src/jalview/ext/ensembl/EnsemblProtein.java index 1554a0b..99006aa 100644 --- a/src/jalview/ext/ensembl/EnsemblProtein.java +++ b/src/jalview/ext/ensembl/EnsemblProtein.java @@ -23,8 +23,6 @@ package jalview.ext.ensembl; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceFeature; -import java.util.List; - import com.stevesoft.pat.Regex; /** diff --git a/src/jalview/ext/ensembl/EnsemblRestClient.java b/src/jalview/ext/ensembl/EnsemblRestClient.java index c06d13e..b1bc8e5 100644 --- a/src/jalview/ext/ensembl/EnsemblRestClient.java +++ b/src/jalview/ext/ensembl/EnsemblRestClient.java @@ -43,8 +43,6 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; -import com.stevesoft.pat.Regex; - /** * Base class for Ensembl REST service clients * @@ -68,9 +66,9 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher * @see https://github.com/Ensembl/ensembl-rest/wiki/Change-log * @see http://rest.ensembl.org/info/rest?content-type=application/json */ - private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "5.0"; + private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "6.0"; - private static final String LATEST_ENSEMBL_REST_VERSION = "5.0"; + private static final String LATEST_ENSEMBL_REST_VERSION = "6.1"; private static final String REST_CHANGE_LOG = "https://github.com/Ensembl/ensembl-rest/wiki/Change-log"; @@ -83,18 +81,11 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher private final static long VERSION_RETEST_INTERVAL = 1000L * 3600; // 1 hr - private static final Regex PROTEIN_REGEX = new Regex( - "(ENS)([A-Z]{3}|)P[0-9]{11}$"); - - private static final Regex TRANSCRIPT_REGEX = new Regex( - "(ENS)([A-Z]{3}|)T[0-9]{11}$"); - - private static final Regex GENE_REGEX = new Regex( - "(ENS)([A-Z]{3}|)G[0-9]{11}$"); + protected static final String CONTENT_TYPE_JSON = "?content-type=application/json"; static { - domainData = new HashMap(); + domainData = new HashMap<>(); domainData.put(ENSEMBL_REST, new EnsemblInfo(ENSEMBL_REST, LATEST_ENSEMBL_REST_VERSION)); domainData.put(ENSEMBL_GENOMES_REST, new EnsemblInfo( @@ -121,42 +112,6 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher setDomain(d); } - /** - * Answers true if the query matches the regular expression pattern for an - * Ensembl transcript stable identifier - * - * @param query - * @return - */ - public boolean isTranscriptIdentifier(String query) - { - return query == null ? false : TRANSCRIPT_REGEX.search(query); - } - - /** - * Answers true if the query matches the regular expression pattern for an - * Ensembl protein stable identifier - * - * @param query - * @return - */ - public boolean isProteinIdentifier(String query) - { - return query == null ? false : PROTEIN_REGEX.search(query); - } - - /** - * Answers true if the query matches the regular expression pattern for an - * Ensembl gene stable identifier - * - * @param query - * @return - */ - public boolean isGeneIdentifier(String query) - { - return query == null ? false : GENE_REGEX.search(query); - } - @Override public boolean queryInProgress() { @@ -218,8 +173,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher { // note this format works for both ensembl and ensemblgenomes // info/ping.json works for ensembl only (March 2016) - URL ping = new URL( - getDomain() + "/info/ping?content-type=application/json"); + URL ping = new URL(getDomain() + "/info/ping" + CONTENT_TYPE_JSON); /* * expect {"ping":1} if ok @@ -228,6 +182,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher br = getHttpResponse(ping, null, 2 * 1000); if (br == null) { + // error reponse status return false; } JSONParser jp = new JSONParser(); @@ -506,9 +461,12 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher URL url = null; try { - url = new URL( - getDomain() + "/info/rest?content-type=application/json"); + url = new URL(getDomain() + "/info/rest" + CONTENT_TYPE_JSON); BufferedReader br = getHttpResponse(url, null); + if (br == null) + { + return; + } JSONObject val = (JSONObject) jp.parse(br); String version = val.get("release").toString(); String majorVersion = version.substring(0, version.indexOf(".")); @@ -571,8 +529,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher try { - url = new URL( - getDomain() + "/info/data?content-type=application/json"); + url = new URL(getDomain() + "/info/data" + CONTENT_TYPE_JSON); br = getHttpResponse(url, null); if (br != null) { diff --git a/src/jalview/ext/ensembl/EnsemblSymbol.java b/src/jalview/ext/ensembl/EnsemblSymbol.java index e2e38b5..75598a0 100644 --- a/src/jalview/ext/ensembl/EnsemblSymbol.java +++ b/src/jalview/ext/ensembl/EnsemblSymbol.java @@ -42,6 +42,10 @@ import org.json.simple.parser.ParseException; */ public class EnsemblSymbol extends EnsemblXref { + private static final String GENE = "gene"; + private static final String TYPE = "type"; + private static final String ID = "id"; + /** * Constructor given the target domain to fetch data from * @@ -73,8 +77,9 @@ public class EnsemblSymbol extends EnsemblXref while (rvals.hasNext()) { JSONObject val = (JSONObject) rvals.next(); - String id = val.get("id").toString(); - if (id != null && isGeneIdentifier(id)) + String id = val.get(ID).toString(); + String type = val.get(TYPE).toString(); + if (id != null && GENE.equals(type)) { result = id; break; @@ -87,12 +92,31 @@ public class EnsemblSymbol extends EnsemblXref return result; } - protected URL getUrl(String id, Species species) + /** + * Constructs the URL for the REST symbol endpoint + * + * @param id + * the accession id (Ensembl or external) + * @param species + * a species name recognisable by Ensembl + * @param type + * an optional type to filter the response (gene, transcript, + * translation) + * @return + */ + protected URL getUrl(String id, Species species, String... type) { - String url = getDomain() + "/xrefs/symbol/" + species.toString() + "/" - + id + "?content-type=application/json"; + StringBuilder sb = new StringBuilder(); + sb.append(getDomain()).append("/xrefs/symbol/") + .append(species.toString()).append("/").append(id) + .append(CONTENT_TYPE_JSON); + for (String t : type) + { + sb.append("&object_type=").append(t); + } try { + String url = sb.toString(); return new URL(url); } catch (MalformedURLException e) { @@ -107,7 +131,7 @@ public class EnsemblSymbol extends EnsemblXref * @param identifier * @return */ - public List getIds(String identifier) + public List getGeneIds(String identifier) { List result = new ArrayList(); List ids = new ArrayList(); @@ -121,14 +145,15 @@ public class EnsemblSymbol extends EnsemblXref { for (Species taxon : Species.getModelOrganisms()) { - URL url = getUrl(query, taxon); + URL url = getUrl(query, taxon, GENE); if (url != null) { br = getHttpResponse(url, ids); if (br != null) { String geneId = parseSymbolResponse(br); - if (geneId != null) + System.out.println(url + " returned " + geneId); + if (geneId != null && !result.contains(geneId)) { result.add(geneId); } diff --git a/src/jalview/ext/ensembl/EnsemblXref.java b/src/jalview/ext/ensembl/EnsemblXref.java index c002c08..27c448e 100644 --- a/src/jalview/ext/ensembl/EnsemblXref.java +++ b/src/jalview/ext/ensembl/EnsemblXref.java @@ -171,16 +171,13 @@ class EnsemblXref extends EnsemblRestClient while (rvals.hasNext()) { JSONObject val = (JSONObject) rvals.next(); - String dbname = val.get("dbname").toString(); - if (GO_GENE_ONTOLOGY.equals(dbname)) - { - continue; - } + String db = val.get("dbname").toString(); String id = val.get("primary_id").toString(); - if (dbname != null && id != null) + if (db != null && id != null + && !GO_GENE_ONTOLOGY.equals(db)) { - dbname = DBRefUtils.getCanonicalName(dbname); - DBRefEntry dbref = new DBRefEntry(dbname, getXRefVersion(), id); + db = DBRefUtils.getCanonicalName(db); + DBRefEntry dbref = new DBRefEntry(db, getXRefVersion(), id); result.add(dbref); } } @@ -214,7 +211,7 @@ class EnsemblXref extends EnsemblRestClient protected URL getUrl(String identifier) { String url = getDomain() + "/xrefs/id/" + identifier - + "?content-type=application/json&all_levels=1"; + + CONTENT_TYPE_JSON + "&all_levels=1"; try { return new URL(url); diff --git a/src/jalview/ext/jmol/JalviewJmolBinding.java b/src/jalview/ext/jmol/JalviewJmolBinding.java index 50aba62..41bc116 100644 --- a/src/jalview/ext/jmol/JalviewJmolBinding.java +++ b/src/jalview/ext/jmol/JalviewJmolBinding.java @@ -27,6 +27,7 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.HiddenColumns; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; +import jalview.gui.IProgressIndicator; import jalview.io.DataSourceType; import jalview.io.StructureFile; import jalview.schemes.ColourSchemeI; @@ -72,7 +73,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel */ private boolean associateNewStructs = false; - Vector atomsPicked = new Vector(); + Vector atomsPicked = new Vector<>(); private List chainNames; @@ -610,7 +611,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } if (modelFileNames == null) { - List mset = new ArrayList(); + List mset = new ArrayList<>(); _modelFileNameMap = new int[viewer.ms.mc]; String m = viewer.ms.getModelFileName(0); if (m != null) @@ -670,7 +671,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel @Override public synchronized String[] getStructureFiles() { - List mset = new ArrayList(); + List mset = new ArrayList<>(); if (viewer == null) { return new String[0]; @@ -1060,8 +1061,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel fileLoadingError = null; String[] oldmodels = modelFileNames; modelFileNames = null; - chainNames = new ArrayList(); - chainFile = new Hashtable(); + chainNames = new ArrayList<>(); + chainFile = new Hashtable<>(); boolean notifyLoaded = false; String[] modelfilenames = getStructureFiles(); // first check if we've lost any structures @@ -1127,7 +1128,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel // see JAL-623 - need method of matching pasted data up { pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], - pdbfile, DataSourceType.PASTE); + pdbfile, DataSourceType.PASTE, + getIProgressIndicator()); getPdbEntry(modelnum).setFile("INLINE" + pdb.getId()); matches = true; foundEntry = true; @@ -1159,7 +1161,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } // Explicitly map to the filename used by Jmol ; pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], - fileName, protocol); + fileName, protocol, getIProgressIndicator()); // pdbentry[pe].getFile(), protocol); } @@ -1227,6 +1229,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel return chainNames; } + protected abstract IProgressIndicator getIProgressIndicator(); + public void notifyNewPickingModeMeasurement(int iatom, String strMeasure) { notifyAtomPicked(iatom, strMeasure, null); diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 143e672..e166fc1 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -163,8 +163,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, AlignViewport viewport; - ViewportRanges vpRanges; - public AlignViewControllerI avc; List alignPanels = new ArrayList<>(); @@ -336,7 +334,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, progressBar = new ProgressBar(this.statusPanel, this.statusBar); } - vpRanges = viewport.getRanges(); avc = new jalview.controller.AlignViewController(this, viewport, alignPanel); if (viewport.getAlignmentConservationAnnotation() == null) @@ -654,9 +651,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { (viewport.cursorMode ? "on" : "off") })); if (viewport.cursorMode) { - alignPanel.getSeqPanel().seqCanvas.cursorX = vpRanges + ViewportRanges ranges = viewport.getRanges(); + alignPanel.getSeqPanel().seqCanvas.cursorX = ranges .getStartRes(); - alignPanel.getSeqPanel().seqCanvas.cursorY = vpRanges + alignPanel.getSeqPanel().seqCanvas.cursorY = ranges .getStartSeq(); } alignPanel.getSeqPanel().seqCanvas.repaint(); @@ -689,10 +687,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, break; } case KeyEvent.VK_PAGE_UP: - vpRanges.pageUp(); + viewport.getRanges().pageUp(); break; case KeyEvent.VK_PAGE_DOWN: - vpRanges.pageDown(); + viewport.getRanges().pageDown(); break; } } @@ -2147,7 +2145,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { // propagate alignment changed. - vpRanges.setEndSeq(alignment.getHeight()); + viewport.getRanges().setEndSeq(alignment.getHeight()); if (annotationAdded) { // Duplicate sequence annotation in all views. @@ -2548,7 +2546,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { trimRegion = new TrimRegionCommand("Remove Left", true, seqs, column, viewport.getAlignment()); - vpRanges.setStartRes(0); + viewport.getRanges().setStartRes(0); } else { @@ -2613,13 +2611,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, // This is to maintain viewport position on first residue // of first sequence SequenceI seq = viewport.getAlignment().getSequenceAt(0); - int startRes = seq.findPosition(vpRanges.getStartRes()); + ViewportRanges ranges = viewport.getRanges(); + int startRes = seq.findPosition(ranges.getStartRes()); // ShiftList shifts; // viewport.getAlignment().removeGaps(shifts=new ShiftList()); // edit.alColumnChanges=shifts.getInverse(); // if (viewport.hasHiddenColumns) // viewport.getColumnSelection().compensateForEdits(shifts); - vpRanges.setStartRes(seq.findIndex(startRes) - 1); + ranges.setStartRes(seq.findIndex(startRes) - 1); viewport.firePropertyChange("alignment", null, viewport.getAlignment().getSequences()); @@ -2652,12 +2651,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, // This is to maintain viewport position on first residue // of first sequence SequenceI seq = viewport.getAlignment().getSequenceAt(0); - int startRes = seq.findPosition(vpRanges.getStartRes()); + int startRes = seq.findPosition(viewport.getRanges().getStartRes()); addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end, viewport.getAlignment())); - vpRanges.setStartRes(seq.findIndex(startRes) - 1); + viewport.getRanges().setStartRes(seq.findIndex(startRes) - 1); viewport.firePropertyChange("alignment", null, viewport.getAlignment().getSequences()); diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index c22a37d..90271c8 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -76,8 +76,6 @@ public class AlignViewport extends AlignmentViewport { Font font; - TreeModel currentTree = null; - boolean cursorMode = false; boolean antiAlias = false; @@ -448,27 +446,6 @@ public class AlignViewport extends AlignmentViewport } /** - * DOCUMENT ME! - * - * @param tree - * DOCUMENT ME! - */ - public void setCurrentTree(TreeModel tree) - { - currentTree = tree; - } - - /** - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - public TreeModel getCurrentTree() - { - return currentTree; - } - - /** * returns the visible column regions of the alignment * * @param selectedRegionOnly @@ -1110,5 +1087,4 @@ public class AlignViewport extends AlignmentViewport } fr.setTransparency(featureSettings.getTransparency()); } - } diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index ff2ffbb..3a1dbe8 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -76,8 +76,6 @@ public class AlignmentPanel extends GAlignmentPanel implements { public AlignViewport av; - ViewportRanges vpRanges; - OverviewPanel overviewPanel; private SeqPanel seqPanel; @@ -97,9 +95,6 @@ public class AlignmentPanel extends GAlignmentPanel implements private AnnotationLabels alabels; - // this value is set false when selection area being dragged - boolean fastPaint = true; - private int hextent = 0; private int vextent = 0; @@ -124,7 +119,6 @@ public class AlignmentPanel extends GAlignmentPanel implements { alignFrame = af; this.av = av; - vpRanges = av.getRanges(); setSeqPanel(new SeqPanel(av, this)); setIdPanel(new IdPanel(av, this)); @@ -156,11 +150,12 @@ public class AlignmentPanel extends GAlignmentPanel implements // reset the viewport ranges when the alignment panel is resized // in particular, this initialises the end residue value when Jalview // is initialised + ViewportRanges ranges = av.getRanges(); if (av.getWrapAlignment()) { int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth( getSeqPanel().seqCanvas.getWidth()); - vpRanges.setViewportWidth(widthInRes); + ranges.setViewportWidth(widthInRes); } else { @@ -169,8 +164,8 @@ public class AlignmentPanel extends GAlignmentPanel implements int heightInSeq = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight(); - vpRanges.setViewportWidth(widthInRes); - vpRanges.setViewportHeight(heightInSeq); + ranges.setViewportWidth(widthInRes); + ranges.setViewportHeight(heightInSeq); } } @@ -380,6 +375,7 @@ public class AlignmentPanel extends GAlignmentPanel implements int verticalOffset, boolean redrawOverview, boolean centre) { int startv, endv, starts, ends; + ViewportRanges ranges = av.getRanges(); if (results == null || results.isEmpty() || av == null || av.getAlignment() == null) @@ -407,7 +403,7 @@ public class AlignmentPanel extends GAlignmentPanel implements */ if (centre) { - int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1; + int offset = (ranges.getEndRes() - ranges.getStartRes() + 1) / 2 - 1; start = Math.max(start - offset, 0); end = end + offset - 1; } @@ -443,33 +439,33 @@ public class AlignmentPanel extends GAlignmentPanel implements if (!av.getWrapAlignment()) { - if ((startv = vpRanges.getStartRes()) >= start) + if ((startv = ranges.getStartRes()) >= start) { /* * Scroll left to make start of search results visible */ setScrollValues(start, seqIndex); } - else if ((endv = vpRanges.getEndRes()) <= end) + else if ((endv = ranges.getEndRes()) <= end) { /* * Scroll right to make end of search results visible */ setScrollValues(startv + end - endv, seqIndex); } - else if ((starts = vpRanges.getStartSeq()) > seqIndex) + else if ((starts = ranges.getStartSeq()) > seqIndex) { /* * Scroll up to make start of search results visible */ - setScrollValues(vpRanges.getStartRes(), seqIndex); + setScrollValues(ranges.getStartRes(), seqIndex); } - else if ((ends = vpRanges.getEndSeq()) <= seqIndex) + else if ((ends = ranges.getEndSeq()) <= seqIndex) { /* * Scroll down to make end of search results visible */ - setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends + setScrollValues(ranges.getStartRes(), starts + seqIndex - ends + 1); } /* @@ -479,7 +475,7 @@ public class AlignmentPanel extends GAlignmentPanel implements } else { - scrollNeeded = vpRanges.scrollToWrappedVisible(start); + scrollNeeded = ranges.scrollToWrappedVisible(start); } paintAlignment(redrawOverview, false); @@ -608,7 +604,8 @@ public class AlignmentPanel extends GAlignmentPanel implements fontChanged(); setAnnotationVisible(av.isShowAnnotation()); boolean wrap = av.getWrapAlignment(); - vpRanges.setStartSeq(0); + ViewportRanges ranges = av.getRanges(); + ranges.setStartSeq(0); scalePanelHolder.setVisible(!wrap); hscroll.setVisible(!wrap); idwidthAdjuster.setVisible(!wrap); @@ -631,16 +628,16 @@ public class AlignmentPanel extends GAlignmentPanel implements { int widthInRes = getSeqPanel().seqCanvas .getWrappedCanvasWidth(canvasWidth); - vpRanges.setViewportWidth(widthInRes); + ranges.setViewportWidth(widthInRes); } else { - int widthInRes = (canvasWidth / av.getCharWidth()) - 1; + int widthInRes = (canvasWidth / av.getCharWidth()); int heightInSeq = (getSeqPanel().seqCanvas.getHeight() - / av.getCharHeight()) - 1; + / av.getCharHeight()); - vpRanges.setViewportWidth(widthInRes); - vpRanges.setViewportHeight(heightInSeq); + ranges.setViewportWidth(widthInRes); + ranges.setViewportHeight(heightInSeq); } } @@ -739,10 +736,12 @@ public class AlignmentPanel extends GAlignmentPanel implements return; } + ViewportRanges ranges = av.getRanges(); + if (evt.getSource() == hscroll) { - int oldX = vpRanges.getStartRes(); - int oldwidth = vpRanges.getViewportWidth(); + int oldX = ranges.getStartRes(); + int oldwidth = ranges.getViewportWidth(); int x = hscroll.getValue(); int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth(); @@ -753,12 +752,12 @@ public class AlignmentPanel extends GAlignmentPanel implements { return; } - vpRanges.setViewportStartAndWidth(x, width); + ranges.setViewportStartAndWidth(x, width); } else if (evt.getSource() == vscroll) { - int oldY = vpRanges.getStartSeq(); - int oldheight = vpRanges.getViewportHeight(); + int oldY = ranges.getStartSeq(); + int oldheight = ranges.getViewportHeight(); int y = vscroll.getValue(); int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight(); @@ -769,12 +768,9 @@ public class AlignmentPanel extends GAlignmentPanel implements { return; } - vpRanges.setViewportStartAndHeight(y, height); - } - if (!fastPaint) - { - repaint(); + ranges.setViewportStartAndHeight(y, height); } + repaint(); } /** @@ -789,6 +785,8 @@ public class AlignmentPanel extends GAlignmentPanel implements { return; // no horizontal scroll when wrapped } + final ViewportRanges ranges = av.getRanges(); + if (evt.getSource() == vscroll) { int newY = vscroll.getValue(); @@ -798,8 +796,8 @@ public class AlignmentPanel extends GAlignmentPanel implements * this prevents infinite recursion of events when the scroll/viewport * ranges values are the same */ - int oldX = vpRanges.getStartRes(); - int oldY = vpRanges.getWrappedScrollPosition(oldX); + int oldX = ranges.getStartRes(); + int oldY = ranges.getWrappedScrollPosition(oldX); if (oldY == newY) { return; @@ -809,9 +807,9 @@ public class AlignmentPanel extends GAlignmentPanel implements /* * limit page up/down to one width's worth of positions */ - int rowSize = vpRanges.getViewportWidth(); + int rowSize = ranges.getViewportWidth(); int newX = newY > oldY ? oldX + rowSize : oldX - rowSize; - vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize); + ranges.setViewportStartAndWidth(Math.max(0, newX), rowSize); } } else @@ -832,8 +830,8 @@ public class AlignmentPanel extends GAlignmentPanel implements "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences"); // scroll to start of panel - vpRanges.setStartRes(0); - vpRanges.setStartSeq(0); + ranges.setStartRes(0); + ranges.setStartSeq(0); } }); } @@ -886,7 +884,8 @@ public class AlignmentPanel extends GAlignmentPanel implements /* * set scroll bar positions */ - setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq()); + ViewportRanges ranges = av.getRanges(); + setScrollValues(ranges.getStartRes(), ranges.getStartSeq()); } /** @@ -898,8 +897,9 @@ public class AlignmentPanel extends GAlignmentPanel implements */ private void setScrollingForWrappedPanel(int topLeftColumn) { - int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn); - int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn); + ViewportRanges ranges = av.getRanges(); + int scrollPosition = ranges.getWrappedScrollPosition(topLeftColumn); + int maxScroll = ranges.getWrappedMaxScroll(topLeftColumn); /* * a scrollbar's value can be set to at most (maximum-extent) @@ -1610,13 +1610,14 @@ public class AlignmentPanel extends GAlignmentPanel implements if (annotationPanel != null) { annotationPanel.dispose(); + annotationPanel = null; } if (av != null) { av.removePropertyChangeListener(propertyChangeListener); - jalview.structure.StructureSelectionManager ssm = av - .getStructureSelectionManager(); + propertyChangeListener = null; + StructureSelectionManager ssm = av.getStructureSelectionManager(); ssm.removeStructureViewerListener(getSeqPanel(), null); ssm.removeSelectionListener(getSeqPanel()); ssm.removeCommandListener(av); @@ -1639,9 +1640,15 @@ public class AlignmentPanel extends GAlignmentPanel implements */ protected void closeChildFrames() { + if (overviewPanel != null) + { + overviewPanel.dispose(); + overviewPanel = null; + } if (calculationDialog != null) { calculationDialog.closeFrame(); + calculationDialog = null; } } @@ -1888,8 +1895,9 @@ public class AlignmentPanel extends GAlignmentPanel implements public void propertyChange(PropertyChangeEvent evt) { // update this panel's scroll values based on the new viewport ranges values - int x = vpRanges.getStartRes(); - int y = vpRanges.getStartSeq(); + ViewportRanges ranges = av.getRanges(); + int x = ranges.getStartRes(); + int y = ranges.getStartSeq(); setScrollValues(x, y); // now update any complementary alignment (its viewport ranges object diff --git a/src/jalview/gui/AppJmol.java b/src/jalview/gui/AppJmol.java index a4597d3..fef7451 100644 --- a/src/jalview/gui/AppJmol.java +++ b/src/jalview/gui/AppJmol.java @@ -157,6 +157,11 @@ public class AppJmol extends StructureViewerBase IProgressIndicator progressBar = null; + @Override + protected IProgressIndicator getIProgressIndicator() + { + return progressBar; + } /** * add a single PDB structure to a new or existing Jmol view * @@ -248,7 +253,7 @@ public class AppJmol extends StructureViewerBase @Override protected List getViewersFor(AlignmentPanel apanel) { - List result = new ArrayList(); + List result = new ArrayList<>(); JInternalFrame[] frames = Desktop.instance.getAllFrames(); for (JInternalFrame frame : frames) @@ -300,7 +305,7 @@ public class AppJmol extends StructureViewerBase @Override void showSelectedChains() { - Vector toshow = new Vector(); + Vector toshow = new Vector<>(); for (int i = 0; i < chainMenu.getItemCount(); i++) { if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem) @@ -489,7 +494,7 @@ public class AppJmol extends StructureViewerBase // todo - record which pdbids were successfully imported. StringBuilder errormsgs = new StringBuilder(); - List files = new ArrayList(); + List files = new ArrayList<>(); String pdbid = ""; try { diff --git a/src/jalview/gui/AppJmolBinding.java b/src/jalview/gui/AppJmolBinding.java index 9325172..724cec1 100644 --- a/src/jalview/gui/AppJmolBinding.java +++ b/src/jalview/gui/AppJmolBinding.java @@ -49,6 +49,12 @@ public class AppJmolBinding extends JalviewJmolBinding } @Override + protected IProgressIndicator getIProgressIndicator() + { + return appJmolWindow.progressBar; + } + + @Override public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment) { return new SequenceRenderer(((AlignmentPanel) alignment).av); diff --git a/src/jalview/gui/AquaInternalFrameManager.java b/src/jalview/gui/AquaInternalFrameManager.java new file mode 100644 index 0000000..ea809eb --- /dev/null +++ b/src/jalview/gui/AquaInternalFrameManager.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jalview.gui; + +import java.awt.Container; +import java.beans.PropertyVetoException; +import java.util.Vector; + +import javax.swing.DefaultDesktopManager; +import javax.swing.DesktopManager; +import javax.swing.JInternalFrame; + +/** + * Based on AquaInternalFrameManager + * + * DesktopManager implementation for Aqua + * + * Mac is more like Windows than it's like Motif/Basic + * + * From WindowsDesktopManager: + * + * This class implements a DesktopManager which more closely follows the MDI + * model than the DefaultDesktopManager. Unlike the DefaultDesktopManager + * policy, MDI requires that the selected and activated child frames are the + * same, and that that frame always be the top-most window. + *

    + * The maximized state is managed by the DesktopManager with MDI, instead of + * just being a property of the individual child frame. This means that if the + * currently selected window is maximized and another window is selected, that + * new window will be maximized. + * + * Downloaded from + * https://raw.githubusercontent.com/frohoff/jdk8u-jdk/master/src/macosx/classes/com/apple/laf/AquaInternalFrameManager.java + * + * Patch from Jim Procter - when the most recently opened frame is closed, + * correct behaviour is to go to the next most recent frame, rather than wrap + * around to the bottom of the window stack (as the original implementation + * does) + * + * @see com.sun.java.swing.plaf.windows.WindowsDesktopManager + */ +public class AquaInternalFrameManager extends DefaultDesktopManager +{ + // Variables + + /* The frame which is currently selected/activated. + * We store this value to enforce Mac's single-selection model. + */ + JInternalFrame fCurrentFrame; + + JInternalFrame fInitialFrame; + + /* The list of frames, sorted by order of creation. + * This list is necessary because by default the order of + * child frames in the JDesktopPane changes during frame + * activation (the activated frame is moved to index 0). + * We preserve the creation order so that "next" and "previous" + * frame actions make sense. + */ + Vector fChildFrames = new Vector<>(1); + + /** + * keep a reference to the original LAF manager so we can iconise/de-iconise + * correctly + */ + private DesktopManager ourManager; + + public AquaInternalFrameManager(DesktopManager desktopManager) + { + ourManager = desktopManager; + } + + @Override + public void closeFrame(final JInternalFrame f) + { + if (f == fCurrentFrame) + { + boolean mostRecentFrame = fChildFrames + .indexOf(f) == fChildFrames.size() - 1; + if (!mostRecentFrame) + { + activateNextFrame(); + } + else + { + activatePreviousFrame(); + } + } + fChildFrames.removeElement(f); + super.closeFrame(f); + } + + @Override + public void deiconifyFrame(final JInternalFrame f) + { + JInternalFrame.JDesktopIcon desktopIcon; + + desktopIcon = f.getDesktopIcon(); + // If the icon moved, move the frame to that spot before expanding it + // reshape does delta checks for us + f.reshape(desktopIcon.getX(), desktopIcon.getY(), f.getWidth(), + f.getHeight()); + ourManager.deiconifyFrame(f); + } + + void addIcon(final Container c, + final JInternalFrame.JDesktopIcon desktopIcon) + { + c.add(desktopIcon); + } + + /** + * Removes the frame from its parent and adds its desktopIcon to the parent. + */ + @Override + public void iconifyFrame(final JInternalFrame f) + { + ourManager.iconifyFrame(f); + } + + // WindowsDesktopManager code + @Override + public void activateFrame(final JInternalFrame f) + { + try + { + if (f != null) + { + super.activateFrame(f); + } + + // If this is the first activation, add to child list. + if (fChildFrames.indexOf(f) == -1) + { + fChildFrames.addElement(f); + } + + if (fCurrentFrame != null && f != fCurrentFrame) + { + if (fCurrentFrame.isSelected()) + { + fCurrentFrame.setSelected(false); + } + } + + if (f != null && !f.isSelected()) + { + f.setSelected(true); + } + + fCurrentFrame = f; + } catch (final PropertyVetoException e) + { + } + } + + private void switchFrame(final boolean next) + { + if (fCurrentFrame == null) + { + // initialize first frame we find + if (fInitialFrame != null) + { + activateFrame(fInitialFrame); + } + return; + } + + final int count = fChildFrames.size(); + if (count <= 1) + { + // No other child frames. + return; + } + + final int currentIndex = fChildFrames.indexOf(fCurrentFrame); + if (currentIndex == -1) + { + // the "current frame" is no longer in the list + fCurrentFrame = null; + return; + } + + int nextIndex; + if (next) + { + nextIndex = currentIndex + 1; + if (nextIndex == count) + { + nextIndex = 0; + } + } + else + { + nextIndex = currentIndex - 1; + if (nextIndex == -1) + { + nextIndex = count - 1; + } + } + final JInternalFrame f = fChildFrames.elementAt(nextIndex); + activateFrame(f); + fCurrentFrame = f; + } + + /** + * Activate the next child JInternalFrame, as determined by the frames' + * Z-order. If there is only one child frame, it remains activated. If there + * are no child frames, nothing happens. + */ + public void activateNextFrame() + { + switchFrame(true); + } + + /** + * same as above but will activate a frame if none have been selected + */ + public void activateNextFrame(final JInternalFrame f) + { + fInitialFrame = f; + switchFrame(true); + } + + /** + * Activate the previous child JInternalFrame, as determined by the frames' + * Z-order. If there is only one child frame, it remains activated. If there + * are no child frames, nothing happens. + */ + public void activatePreviousFrame() + { + switchFrame(false); + } +} \ No newline at end of file diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index a9f3966..e403dba 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -105,6 +105,11 @@ public class CalculationChooser extends JPanel List tips = new ArrayList(); + /* + * the most recently opened PCA results panel + */ + private PCAPanel pcaPanel; + /** * Constructor * @@ -534,7 +539,7 @@ public class CalculationChooser extends JPanel JvOptionPane.WARNING_MESSAGE); return; } - new PCAPanel(af.alignPanel, modelName, params); + pcaPanel = new PCAPanel(af.alignPanel, modelName, params); } /** @@ -592,4 +597,9 @@ public class CalculationChooser extends JPanel { } } + + public PCAPanel getPcaPanel() + { + return pcaPanel; + } } diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java index ba360af..89de2e8 100644 --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@ -358,7 +358,7 @@ public class ChimeraViewFrame extends StructureViewerBase @Override protected List getViewersFor(AlignmentPanel ap) { - List result = new ArrayList(); + List result = new ArrayList<>(); JInternalFrame[] frames = Desktop.instance.getAllFrames(); for (JInternalFrame frame : frames) @@ -414,7 +414,7 @@ public class ChimeraViewFrame extends StructureViewerBase @Override void showSelectedChains() { - List toshow = new ArrayList(); + List toshow = new ArrayList<>(); for (int i = 0; i < chainMenu.getItemCount(); i++) { if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem) @@ -484,8 +484,8 @@ public class ChimeraViewFrame extends StructureViewerBase // todo - record which pdbids were successfully imported. StringBuilder errormsgs = new StringBuilder(128); StringBuilder files = new StringBuilder(128); - List filePDB = new ArrayList(); - List filePDBpos = new ArrayList(); + List filePDB = new ArrayList<>(); + List filePDBpos = new ArrayList<>(); PDBEntry thePdbEntry = null; StructureFile pdb = null; try @@ -598,9 +598,12 @@ public class ChimeraViewFrame extends StructureViewerBase stopProgressBar("", startTime); } // Explicitly map to the filename used by Chimera ; + pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos], - jmb.getChains()[pos], pe.getFile(), protocol); + jmb.getChains()[pos], pe.getFile(), protocol, + progressBar); stashFoundChains(pdb, pe.getFile()); + } catch (OutOfMemoryError oomerror) { new OOMWarning( @@ -658,7 +661,7 @@ public class ChimeraViewFrame extends StructureViewerBase /** * Fetch PDB data and save to a local file. Returns the full path to the file, - * or null if fetch fails. + * or null if fetch fails. TODO: refactor to common with Jmol ? duplication * * @param processingEntry * @return @@ -891,4 +894,10 @@ public class ChimeraViewFrame extends StructureViewerBase } return reply; } + + @Override + protected IProgressIndicator getIProgressIndicator() + { + return progressBar; + } } diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 1f8983f..2d1ba12 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -68,8 +68,6 @@ import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -361,7 +359,10 @@ public class Desktop extends jalview.jbgui.GDesktop desktop.setDesktopManager( new MyDesktopManager( (Platform.isWindows() ? new DefaultDesktopManager() - : desktop.getDesktopManager()))); + : Platform.isAMac() + ? new AquaInternalFrameManager( + desktop.getDesktopManager()) + : desktop.getDesktopManager()))); Rectangle dims = getLastKnownDimensions(""); if (dims != null) @@ -431,24 +432,6 @@ public class Desktop extends jalview.jbgui.GDesktop }); desktop.addMouseListener(ma); - this.addFocusListener(new FocusListener() - { - - @Override - public void focusLost(FocusEvent e) - { - // TODO Auto-generated method stub - - } - - @Override - public void focusGained(FocusEvent e) - { - Cache.log.debug("Relaying windows after focus gain"); - // make sure that we sort windows properly after we gain focus - instance.relayerWindows(); - } - }); this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this)); // Spawn a thread that shows the splashscreen SwingUtilities.invokeLater(new Runnable() @@ -886,6 +869,10 @@ public class Desktop extends jalview.jbgui.GDesktop JInternalFrame itf = desktop.getSelectedFrame(); if (itf != null) { + if (itf instanceof AlignFrame) + { + Jalview.setCurrentAlignFrame((AlignFrame) itf); + } itf.requestFocus(); } } @@ -912,15 +899,7 @@ public class Desktop extends jalview.jbgui.GDesktop menuItem.removeActionListener(menuItem.getActionListeners()[0]); } windowMenu.remove(menuItem); - JInternalFrame itf = desktop.getSelectedFrame(); - if (itf != null) - { - itf.requestFocus(); - if (itf instanceof AlignFrame) - { - Jalview.setCurrentAlignFrame((AlignFrame) itf); - } - } + System.gc(); }; }); @@ -2512,14 +2491,6 @@ public class Desktop extends jalview.jbgui.GDesktop } } - /** - * fixes stacking order after a modal dialog to ensure windows that should be - * on top actually are - */ - public void relayerWindows() - { - - } /** * Accessor method to quickly get all the AlignmentFrames loaded. diff --git a/src/jalview/gui/IProgressIndicator.java b/src/jalview/gui/IProgressIndicator.java index 981e94c..35bd871 100644 --- a/src/jalview/gui/IProgressIndicator.java +++ b/src/jalview/gui/IProgressIndicator.java @@ -34,7 +34,8 @@ public interface IProgressIndicator * is removed with a second call with same ID. * * @param message - * - displayed message for operation + * - displayed message for operation. Please ensure message is + * internationalised. * @param id * - unique handle for this indicator */ diff --git a/src/jalview/gui/IdPanel.java b/src/jalview/gui/IdPanel.java index a46c2c1..1f2a3ad 100755 --- a/src/jalview/gui/IdPanel.java +++ b/src/jalview/gui/IdPanel.java @@ -154,7 +154,7 @@ public class IdPanel extends JPanel { av.getRanges().scrollRight(true); } - else if (!av.getWrapAlignment()) + else { av.getRanges().scrollUp(false); } @@ -165,7 +165,7 @@ public class IdPanel extends JPanel { av.getRanges().scrollRight(false); } - else if (!av.getWrapAlignment()) + else { av.getRanges().scrollUp(true); } diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index b357234..9e319c1 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -1084,7 +1084,7 @@ public class Jalview2XML // SAVE TREES // ///////////////////////////////// - if (!storeDS && av.currentTree != null) + if (!storeDS && av.getCurrentTree() != null) { // FIND ANY ASSOCIATED TREES // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT @@ -1102,7 +1102,7 @@ public class Jalview2XML { Tree tree = new Tree(); tree.setTitle(tp.getTitle()); - tree.setCurrentTree((av.currentTree == tp.getTree())); + tree.setCurrentTree((av.getCurrentTree() == tp.getTree())); tree.setNewick(tp.getTree().print()); tree.setThreshold(tp.treeCanvas.threshold); @@ -4250,7 +4250,8 @@ public class Jalview2XML StructureData filedat = oldFiles.get(id); String pdbFile = filedat.getFilePath(); SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]); - binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE); + binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE, + null); binding.addSequenceForStructFile(pdbFile, seq); } // and add the AlignmentPanel's reference to the view panel diff --git a/src/jalview/gui/OverviewPanel.java b/src/jalview/gui/OverviewPanel.java index 51d7a84..9ddb751 100755 --- a/src/jalview/gui/OverviewPanel.java +++ b/src/jalview/gui/OverviewPanel.java @@ -40,8 +40,10 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.beans.PropertyChangeEvent; +import java.beans.PropertyVetoException; import javax.swing.JCheckBoxMenuItem; +import javax.swing.JInternalFrame; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; @@ -350,8 +352,22 @@ public class OverviewPanel extends JPanel { try { - av.getRanges().removePropertyChangeListener(this); + if (av != null) + { + av.getRanges().removePropertyChangeListener(this); + } + oviewCanvas.dispose(); + + /* + * close the parent frame (which also removes it from the + * Desktop Windows menu) + */ + ((JInternalFrame) SwingUtilities.getAncestorOfClass( + JInternalFrame.class, (this))).setClosed(true); + } catch (PropertyVetoException e) + { + // ignore } finally { progressPanel = null; diff --git a/src/jalview/gui/PCAPanel.java b/src/jalview/gui/PCAPanel.java index f861a7c..9f52d26 100644 --- a/src/jalview/gui/PCAPanel.java +++ b/src/jalview/gui/PCAPanel.java @@ -79,6 +79,8 @@ public class PCAPanel extends GPCAPanel int top = 0; + private boolean working; + /** * Creates a new PCAPanel object using default score model and parameters * @@ -234,6 +236,7 @@ public class PCAPanel extends GPCAPanel message = MessageManager.getString("label.pca_calculating"); } progress.setProgressBar(message, progId); + working = true; try { calcSettings.setEnabled(false); @@ -252,6 +255,7 @@ public class PCAPanel extends GPCAPanel } catch (OutOfMemoryError er) { new OOMWarning("calculating PCA", er); + working = false; return; } finally { @@ -266,6 +270,7 @@ public class PCAPanel extends GPCAPanel .getString("label.principal_component_analysis"), 475, 450); this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); } + working = false; } @Override @@ -788,4 +793,14 @@ public class PCAPanel extends GPCAPanel top = t; zCombobox.setSelectedIndex(2); } + + /** + * Answers true if PCA calculation is in progress, else false + * + * @return + */ + public boolean isWorking() + { + return working; + } } diff --git a/src/jalview/gui/PaintRefresher.java b/src/jalview/gui/PaintRefresher.java index d731e70..ced5544 100755 --- a/src/jalview/gui/PaintRefresher.java +++ b/src/jalview/gui/PaintRefresher.java @@ -26,9 +26,9 @@ import jalview.datamodel.SequenceI; import java.awt.Component; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * Route datamodel/view update events for a sequence set to any display @@ -74,26 +74,21 @@ public class PaintRefresher */ public static void RemoveComponent(Component comp) { - List emptied = new ArrayList(); - for (Entry> registered : components.entrySet()) + if (components == null) { - String id = registered.getKey(); - List comps = components.get(id); + return; + } + + Iterator it = components.keySet().iterator(); + while (it.hasNext()) + { + List comps = components.get(it.next()); comps.remove(comp); if (comps.isEmpty()) { - emptied.add(id); + it.remove(); } } - - /* - * Remove now empty ids after the above (to avoid - * ConcurrentModificationException). - */ - for (String id : emptied) - { - components.remove(id); - } } public static void Refresh(Component source, String id) diff --git a/src/jalview/gui/PairwiseAlignPanel.java b/src/jalview/gui/PairwiseAlignPanel.java index f75407c..d081794 100755 --- a/src/jalview/gui/PairwiseAlignPanel.java +++ b/src/jalview/gui/PairwiseAlignPanel.java @@ -22,7 +22,8 @@ package jalview.gui; import jalview.analysis.AlignSeq; import jalview.datamodel.Alignment; -import jalview.datamodel.Sequence; +import jalview.datamodel.AlignmentView; +import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.jbgui.GPairwiseAlignPanel; import jalview.util.MessageManager; @@ -40,49 +41,56 @@ import java.util.Vector; public class PairwiseAlignPanel extends GPairwiseAlignPanel { + private static final String DASHES = "---------------------\n"; + AlignmentViewport av; - Vector sequences; + Vector sequences; /** * Creates a new PairwiseAlignPanel object. * - * @param av + * @param viewport * DOCUMENT ME! */ - public PairwiseAlignPanel(AlignmentViewport av) + public PairwiseAlignPanel(AlignmentViewport viewport) { super(); - this.av = av; + this.av = viewport; - sequences = new Vector(); + sequences = new Vector(); - SequenceI[] seqs; - String[] seqStrings = av.getViewAsString(true); + SequenceGroup selectionGroup = viewport.getSelectionGroup(); + boolean isSelection = selectionGroup != null + && selectionGroup.getSize() > 0; + AlignmentView view = viewport.getAlignmentView(isSelection); + // String[] seqStrings = viewport.getViewAsString(true); + String[] seqStrings = view.getSequenceStrings(viewport + .getGapCharacter()); - if (av.getSelectionGroup() == null) + SequenceI[] seqs; + if (isSelection) { - seqs = av.getAlignment().getSequencesArray(); + seqs = (SequenceI[]) view.getAlignmentAndHiddenColumns(viewport + .getGapCharacter())[0]; } else { - seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment()); + seqs = av.getAlignment().getSequencesArray(); } - String type = (av.getAlignment().isNucleotide()) ? AlignSeq.DNA + String type = (viewport.getAlignment().isNucleotide()) ? AlignSeq.DNA : AlignSeq.PEP; float[][] scores = new float[seqs.length][seqs.length]; - double totscore = 0; + double totscore = 0D; int count = seqs.length; - - Sequence seq; + boolean first = true; for (int i = 1; i < count; i++) { for (int j = 0; j < i; j++) { - AlignSeq as = new AlignSeq(seqs[i], seqStrings[i], seqs[j], seqStrings[j], type); @@ -94,9 +102,15 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel as.calcScoreMatrix(); as.traceAlignment(); + if (!first) + { + System.out.println(DASHES); + textarea.append(DASHES); + } + first = false; as.printAlignment(System.out); - scores[i][j] = (float) as.getMaxScore() - / (float) as.getASeq1().length; + scores[i][j] = as.getMaxScore() + / as.getASeq1().length; totscore = totscore + scores[i][j]; textarea.append(as.getOutput()); @@ -107,28 +121,53 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel if (count > 2) { - System.out.println( - "Pairwise alignment scaled similarity score matrix\n"); + printScoreMatrix(seqs, scores, totscore); + } + } - for (int i = 0; i < count; i++) - { - jalview.util.Format.print(System.out, "%s \n", - ("" + i) + " " + seqs[i].getName()); - } + /** + * Prints a matrix of seqi-seqj pairwise alignment scores to sysout + * + * @param seqs + * @param scores + * @param totscore + */ + protected void printScoreMatrix(SequenceI[] seqs, float[][] scores, + double totscore) + { + System.out + .println("Pairwise alignment scaled similarity score matrix\n"); - System.out.println("\n"); + for (int i = 0; i < seqs.length; i++) + { + System.out.println(String.format("%3d %s", i + 1, + seqs[i].getDisplayId(true))); + } + + /* + * table heading columns for sequences 1, 2, 3... + */ + System.out.print("\n "); + for (int i = 0; i < seqs.length; i++) + { + System.out.print(String.format("%7d", i + 1)); + } + System.out.println(); - for (int i = 0; i < count; i++) + for (int i = 0; i < seqs.length; i++) + { + System.out.print(String.format("%3d", i + 1)); + for (int j = 0; j < i; j++) { - for (int j = 0; j < i; j++) - { - jalview.util.Format.print(System.out, "%7.3f", - scores[i][j] / totscore); - } + /* + * as a fraction of tot score, outputs are 0 <= score <= 1 + */ + System.out.print(String.format("%7.3f", scores[i][j] / totscore)); } - - System.out.println("\n"); + System.out.println(); } + + System.out.println("\n"); } /** @@ -137,13 +176,14 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel * @param e * DOCUMENT ME! */ + @Override protected void viewInEditorButton_actionPerformed(ActionEvent e) { - Sequence[] seq = new Sequence[sequences.size()]; + SequenceI[] seq = new SequenceI[sequences.size()]; for (int i = 0; i < sequences.size(); i++) { - seq[i] = (Sequence) sequences.elementAt(i); + seq[i] = sequences.elementAt(i); } AlignFrame af = new AlignFrame(new Alignment(seq), diff --git a/src/jalview/gui/ProgressBar.java b/src/jalview/gui/ProgressBar.java index ea341e3..011d810 100644 --- a/src/jalview/gui/ProgressBar.java +++ b/src/jalview/gui/ProgressBar.java @@ -89,8 +89,8 @@ public class ProgressBar implements IProgressIndicator } this.statusPanel = container; this.statusBar = statusBar; - this.progressBars = new Hashtable(); - this.progressBarHandlers = new Hashtable(); + this.progressBars = new Hashtable<>(); + this.progressBarHandlers = new Hashtable<>(); } @@ -119,46 +119,52 @@ public class ProgressBar implements IProgressIndicator * execution. */ @Override - public void setProgressBar(String message, long id) + public void setProgressBar(final String message, final long id) { - Long longId = Long.valueOf(id); - - JPanel progressPanel = progressBars.get(longId); - if (progressPanel != null) + SwingUtilities.invokeLater(new Runnable() { - /* - * Progress bar is displayed for this id - remove it now, and any handler - */ - progressBars.remove(id); - if (message != null && statusBar != null) - { - statusBar.setText(message); - } - if (progressBarHandlers.containsKey(longId)) + @Override + public void run() { - progressBarHandlers.remove(longId); - } - removeRow(progressPanel); - } - else - { - /* - * No progress bar for this id - add one now - */ - progressPanel = new JPanel(new BorderLayout(10, 5)); + JPanel progressPanel = progressBars.get(id); + if (progressPanel != null) + { + /* + * Progress bar is displayed for this id - remove it now, and any handler + */ + progressBars.remove(id); + if (message != null && statusBar != null) + { + statusBar.setText(message); + } + if (progressBarHandlers.containsKey(id)) + { + progressBarHandlers.remove(id); + } + removeRow(progressPanel); + } + else + { + /* + * No progress bar for this id - add one now + */ + progressPanel = new JPanel(new BorderLayout(10, 5)); - JProgressBar progressBar = new JProgressBar(); - progressBar.setIndeterminate(true); + JProgressBar progressBar = new JProgressBar(); + progressBar.setIndeterminate(true); - progressPanel.add(new JLabel(message), BorderLayout.WEST); - progressPanel.add(progressBar, BorderLayout.CENTER); + progressPanel.add(new JLabel(message), BorderLayout.WEST); + progressPanel.add(progressBar, BorderLayout.CENTER); - addRow(progressPanel); + addRow(progressPanel); - progressBars.put(longId, progressPanel); - } + progressBars.put(id, progressPanel); + } + + refreshLayout(); + } + }); - refreshLayout(); } /** @@ -215,41 +221,50 @@ public class ProgressBar implements IProgressIndicator public void registerHandler(final long id, final IProgressIndicatorHandler handler) { - Long longId = Long.valueOf(id); - final JPanel progressPanel = progressBars.get(longId); - if (progressPanel == null) - { - System.err.println( - "call setProgressBar before registering the progress bar's handler."); - return; - } - - /* - * Nothing useful to do if not a Cancel handler - */ - if (!handler.canCancel()) - { - return; - } - - progressBarHandlers.put(longId, handler); - JButton cancel = new JButton(MessageManager.getString("action.cancel")); final IProgressIndicator us = this; - cancel.addActionListener(new ActionListener() - { + SwingUtilities.invokeLater(new Runnable() + { @Override - public void actionPerformed(ActionEvent e) + public void run() { - handler.cancelActivity(id); - us.setProgressBar(MessageManager - .formatMessage("label.cancelled_params", new Object[] - { ((JLabel) progressPanel.getComponent(0)).getText() }), - id); + final JPanel progressPanel = progressBars.get(id); + if (progressPanel == null) + { + System.err.println( + "call setProgressBar before registering the progress bar's handler."); + return; + } + + /* + * Nothing useful to do if not a Cancel handler + */ + if (!handler.canCancel()) + { + return; + } + + progressBarHandlers.put(id, handler); + JButton cancel = new JButton( + MessageManager.getString("action.cancel")); + cancel.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + handler.cancelActivity(id); + us.setProgressBar(MessageManager + .formatMessage("label.cancelled_params", new Object[] + { ((JLabel) progressPanel.getComponent(0)).getText() }), + id); + } + }); + progressPanel.add(cancel, BorderLayout.EAST); + refreshLayout(); + } }); - progressPanel.add(cancel, BorderLayout.EAST); - refreshLayout(); } } diff --git a/src/jalview/gui/PromptUserConfig.java b/src/jalview/gui/PromptUserConfig.java index 6261015..cb59452 100644 --- a/src/jalview/gui/PromptUserConfig.java +++ b/src/jalview/gui/PromptUserConfig.java @@ -24,8 +24,6 @@ import jalview.bin.Cache; import java.awt.Component; -import javax.swing.JOptionPane; - public class PromptUserConfig implements Runnable { /** @@ -120,6 +118,7 @@ public class PromptUserConfig implements Runnable this.allowCancel = allowCancel; } + @Override public void run() { if (property == null) @@ -206,12 +205,7 @@ public class PromptUserConfig implements Runnable (allowCancel) ? JvOptionPane.YES_NO_CANCEL_OPTION : JvOptionPane.YES_NO_OPTION, JvOptionPane.QUESTION_MESSAGE); - // now, ask the desktop to relayer any external windows that might have - // been obsured - if (Desktop.instance != null) - { - Desktop.instance.relayerWindows(); - } + // and finish parsing the result jalview.bin.Cache.log.debug("Got response : " + reply); if (reply == JvOptionPane.YES_OPTION) diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index 4e896a0..2a9c704 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -27,6 +27,7 @@ import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.ScaleRenderer; import jalview.renderer.ScaleRenderer.ScaleMark; +import jalview.util.Comparison; import jalview.viewmodel.ViewportListenerI; import jalview.viewmodel.ViewportRanges; @@ -46,53 +47,56 @@ import java.util.List; import javax.swing.JComponent; /** - * DOCUMENT ME! + * The Swing component on which the alignment sequences, and annotations (if + * shown), are drawn. This includes scales above, left and right (if shown) in + * Wrapped mode, but not the scale above in Unwrapped mode. * - * @author $author$ - * @version $Revision$ */ public class SeqCanvas extends JComponent implements ViewportListenerI { - private static String ZEROS = "0000000000"; + private static final String ZEROS = "0000000000"; final FeatureRenderer fr; - final SequenceRenderer seqRdr; - BufferedImage img; - Graphics2D gg; - AlignViewport av; - boolean fastPaint = false; + int cursorX = 0; - int labelWidthWest; + int cursorY = 0; - int labelWidthEast; + private final SequenceRenderer seqRdr; - int cursorX = 0; + private boolean fastPaint = false; - int cursorY = 0; + private boolean fastpainting = false; - int charHeight = 0; + private AnnotationPanel annotations; - int charWidth = 0; + /* + * measurements for drawing a wrapped alignment + */ + private int labelWidthEast; // label right width in pixels if shown + + private int labelWidthWest; // label left width in pixels if shown - boolean fastpainting = false; + private int wrappedSpaceAboveAlignment; // gap between widths - AnnotationPanel annotations; + private int wrappedRepeatHeightPx; // height in pixels of wrapped width + + private int wrappedVisibleWidths; // number of wrapped widths displayed + + private Graphics2D gg; /** * Creates a new SeqCanvas object. * - * @param av - * DOCUMENT ME! + * @param ap */ public SeqCanvas(AlignmentPanel ap) { this.av = ap.av; - updateViewport(); fr = new FeatureRenderer(ap); seqRdr = new SequenceRenderer(av); setLayout(new BorderLayout()); @@ -112,29 +116,36 @@ public class SeqCanvas extends JComponent implements ViewportListenerI return fr; } - private void updateViewport() - { - charHeight = av.getCharHeight(); - charWidth = av.getCharWidth(); - } - /** - * DOCUMENT ME! + * Draws the scale above a region of a wrapped alignment, consisting of a + * column number every major interval (10 columns). * * @param g - * DOCUMENT ME! + * the graphics context to draw on, positioned at the start (bottom + * left) of the line on which to draw any scale marks * @param startx - * DOCUMENT ME! + * start alignment column (0..) * @param endx - * DOCUMENT ME! + * end alignment column (0..) * @param ypos - * DOCUMENT ME! + * y offset to draw at */ private void drawNorthScale(Graphics g, int startx, int endx, int ypos) { - updateViewport(); - for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx, - endx)) + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + + /* + * white fill the scale space (for the fastPaint case) + */ + g.setColor(Color.white); + g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(), + charHeight * 3 / 2 + 2); + g.setColor(Color.black); + + List marks = new ScaleRenderer().calculateMarks(av, startx, + endx); + for (ScaleMark mark : marks) { int mpos = mark.column; // (i - startx - 1) if (mpos < 0) @@ -149,137 +160,119 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2)); } - g.drawLine((mpos * charWidth) + (charWidth / 2), - (ypos + 2) - (charHeight / 2), - (mpos * charWidth) + (charWidth / 2), ypos - 2); + + /* + * draw a tick mark below the column number, centred on the column; + * height of tick mark is 4 pixels less than half a character + */ + int xpos = (mpos * charWidth) + (charWidth / 2); + g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2); } } } /** - * DOCUMENT ME! + * Draw the scale to the left or right of a wrapped alignment * * @param g - * DOCUMENT ME! + * graphics context, positioned at the start of the scale to be drawn * @param startx - * DOCUMENT ME! + * first column of wrapped width (0.. excluding any hidden columns) * @param endx - * DOCUMENT ME! + * last column of wrapped width (0.. excluding any hidden columns) * @param ypos - * DOCUMENT ME! + * vertical offset at which to begin the scale + * @param left + * if true, scale is left of residues, if false, scale is right */ - void drawWestScale(Graphics g, int startx, int endx, int ypos) + void drawVerticalScale(Graphics g, final int startx, final int endx, + final int ypos, final boolean left) { - FontMetrics fm = getFontMetrics(av.getFont()); - ypos += charHeight; + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); - if (av.hasHiddenColumns()) - { - startx = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(startx); - endx = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(endx); - } + int yPos = ypos + charHeight; + int startX = startx; + int endX = endx; - int maxwidth = av.getAlignment().getWidth(); if (av.hasHiddenColumns()) { - maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth) - 1; + HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns(); + startX = hiddenColumns.adjustForHiddenColumns(startx); + endX = hiddenColumns.adjustForHiddenColumns(endx); } + FontMetrics fm = getFontMetrics(av.getFont()); - // WEST SCALE for (int i = 0; i < av.getAlignment().getHeight(); i++) { SequenceI seq = av.getAlignment().getSequenceAt(i); - int index = startx; - int value = -1; - while (index < endx) + /* + * find sequence position of first non-gapped position - + * to the right if scale left, to the left if scale right + */ + int index = left ? startX : endX; + int value = -1; + while (index >= startX && index <= endX) { - if (jalview.util.Comparison.isGap(seq.getCharAt(index))) + if (!Comparison.isGap(seq.getCharAt(index))) + { + value = seq.findPosition(index); + break; + } + if (left) { index++; - - continue; } - - value = av.getAlignment().getSequenceAt(i).findPosition(index); - - break; - } - - if (value != -1) - { - int x = labelWidthWest - fm.stringWidth(String.valueOf(value)) - - charWidth / 2; - g.drawString(value + "", x, - (ypos + (i * charHeight)) - (charHeight / 5)); - } - } - } - - /** - * DOCUMENT ME! - * - * @param g - * DOCUMENT ME! - * @param startx - * DOCUMENT ME! - * @param endx - * DOCUMENT ME! - * @param ypos - * DOCUMENT ME! - */ - void drawEastScale(Graphics g, int startx, int endx, int ypos) - { - ypos += charHeight; - - if (av.hasHiddenColumns()) - { - endx = av.getAlignment().getHiddenColumns() - .adjustForHiddenColumns(endx); - } - - SequenceI seq; - // EAST SCALE - for (int i = 0; i < av.getAlignment().getHeight(); i++) - { - seq = av.getAlignment().getSequenceAt(i); - int index = endx; - int value = -1; - - while (index > startx) - { - if (jalview.util.Comparison.isGap(seq.getCharAt(index))) + else { index--; - - continue; } - - value = seq.findPosition(index); - - break; } + /* + * white fill the space for the scale + */ + g.setColor(Color.white); + int y = (yPos + (i * charHeight)) - (charHeight / 5); + // fillRect origin is top left of rectangle + g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast, + charHeight + 1); + if (value != -1) { - g.drawString(String.valueOf(value), 0, - (ypos + (i * charHeight)) - (charHeight / 5)); + /* + * draw scale value, right justified within its width less half a + * character width padding on the right + */ + int labelSpace = left ? labelWidthWest : labelWidthEast; + labelSpace -= charWidth / 2; // leave space to the right + String valueAsString = String.valueOf(value); + int labelLength = fm.stringWidth(valueAsString); + int xOffset = labelSpace - labelLength; + g.setColor(Color.black); + g.drawString(valueAsString, xOffset, y); } } } - /** - * need to make this thread safe move alignment rendering in response to - * slider adjustment + * Does a fast paint of an alignment in response to a scroll. Most of the + * visible region is simply copied and shifted, and then any newly visible + * columns or rows are drawn. The scroll may be horizontal or vertical, but + * not both at once. Scrolling may be the result of + *

      + *
    • dragging a scroll bar
    • + *
    • clicking in the scroll bar
    • + *
    • scrolling by trackpad, middle mouse button, or other device
    • + *
    • by moving the box in the Overview window
    • + *
    • programmatically to make a highlighted position visible
    • + *
    * * @param horizontal - * shift along + * columns to shift right (positive) or left (negative) * @param vertical - * shift up or down in repaint + * rows to shift down (positive) or up (negative) */ public void fastPaint(int horizontal, int vertical) { @@ -289,15 +282,19 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } fastpainting = true; fastPaint = true; - updateViewport(); - ViewportRanges ranges = av.getRanges(); - int startRes = ranges.getStartRes(); - int endRes = ranges.getEndRes(); - int startSeq = ranges.getStartSeq(); - int endSeq = ranges.getEndSeq(); - int transX = 0; - int transY = 0; + try + { + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + + ViewportRanges ranges = av.getRanges(); + int startRes = ranges.getStartRes(); + int endRes = ranges.getEndRes(); + int startSeq = ranges.getStartSeq(); + int endSeq = ranges.getEndSeq(); + int transX = 0; + int transY = 0; gg.copyArea(horizontal * charWidth, vertical * charHeight, img.getWidth(), img.getHeight(), -horizontal * charWidth, @@ -340,15 +337,19 @@ public class SeqCanvas extends JComponent implements ViewportListenerI gg.translate(-transX, -transY); repaint(); - fastpainting = false; + } finally + { + fastpainting = false; + } } @Override public void paintComponent(Graphics g) { - super.paintComponent(g); - - updateViewport(); + super.paintComponent(g); + + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); ViewportRanges ranges = av.getRanges(); @@ -411,7 +412,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI g.drawImage(lcimg, 0, 0, this); } } - + /** * Draw an alignment panel for printing * @@ -519,6 +520,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { BufferedImage lcimg = null; + int charWidth = av.getCharWidth(); + int charHeight = av.getCharHeight(); + int width = getWidth(); int height = getHeight(); @@ -558,29 +562,30 @@ public class SeqCanvas extends JComponent implements ViewportListenerI */ public int getWrappedCanvasWidth(int canvasWidth) { - FontMetrics fm = getFontMetrics(av.getFont()); + int charWidth = av.getCharWidth(); - labelWidthEast = 0; - labelWidthWest = 0; + FontMetrics fm = getFontMetrics(av.getFont()); - if (av.getScaleRightWrapped()) + int labelWidth = 0; + + if (av.getScaleRightWrapped() || av.getScaleLeftWrapped()) { - labelWidthEast = getLabelWidth(fm); + labelWidth = getLabelWidth(fm); } - if (av.getScaleLeftWrapped()) - { - labelWidthWest = labelWidthEast > 0 ? labelWidthEast - : getLabelWidth(fm); - } + labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0; + + labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0; return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth; } /** - * Returns a pixel width suitable for showing the largest sequence coordinate - * (end position) in the alignment. Returns 2 plus the number of decimal - * digits to be shown (3 for 1-10, 4 for 11-99 etc). + * Returns a pixel width sufficient to show the largest sequence coordinate + * (end position) in the alignment, calculated as the FontMetrics width of + * zeroes "0000000" limited to the number of decimal digits to be shown (3 for + * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for + * half a character width space on either side. * * @param fm * @return @@ -598,160 +603,305 @@ public class SeqCanvas extends JComponent implements ViewportListenerI maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd()); } - int length = 2; + int length = 0; for (int i = maxWidth; i > 0; i /= 10) { length++; } - return fm.stringWidth(ZEROS.substring(0, length)); + return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth(); } /** - * DOCUMENT ME! + * Draws as many widths of a wrapped alignment as can fit in the visible + * window * * @param g - * DOCUMENT ME! * @param canvasWidth - * DOCUMENT ME! + * available width in pixels * @param canvasHeight - * DOCUMENT ME! - * @param startRes - * DOCUMENT ME! + * available height in pixels + * @param startColumn + * the first column (0...) of the alignment to draw */ - private void drawWrappedPanel(Graphics g, int canvasWidth, - int canvasHeight, int startRes) + public void drawWrappedPanel(Graphics g, int canvasWidth, + int canvasHeight, final int startColumn) { - updateViewport(); - AlignmentI al = av.getAlignment(); + int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth, + canvasHeight); - int labelWidth = 0; - if (av.getScaleRightWrapped() || av.getScaleLeftWrapped()) + av.setWrappedWidth(wrappedWidthInResidues); + + ViewportRanges ranges = av.getRanges(); + ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues); + + /* + * draw one width at a time (including any scales or annotation shown), + * until we have run out of either alignment or vertical space available + */ + int ypos = wrappedSpaceAboveAlignment; + int maxWidth = ranges.getVisibleAlignmentWidth(); + + int start = startColumn; + int currentWidth = 0; + while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth)) { - FontMetrics fm = getFontMetrics(av.getFont()); - labelWidth = getLabelWidth(fm); + int endColumn = Math + .min(maxWidth, start + wrappedWidthInResidues - 1); + drawWrappedWidth(g, ypos, start, endColumn, canvasHeight); + ypos += wrappedRepeatHeightPx; + start += wrappedWidthInResidues; + currentWidth++; } - labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0; - labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0; + drawWrappedDecorators(g, startColumn); + } - int hgap = charHeight; - if (av.getScaleAboveWrapped()) + /** + * Calculates and saves values needed when rendering a wrapped alignment. + * These depend on many factors, including + *
      + *
    • canvas width and height
    • + *
    • number of visible sequences, and height of annotations if shown
    • + *
    • font and character width
    • + *
    • whether scales are shown left, right or above the alignment
    • + *
    + * + * @param canvasWidth + * @param canvasHeight + * @return the number of residue columns in each width + */ + protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight) + { + int charHeight = av.getCharHeight(); + + /* + * vertical space in pixels between wrapped widths of alignment + * - one character height, or two if scale above is drawn + */ + wrappedSpaceAboveAlignment = charHeight + * (av.getScaleAboveWrapped() ? 2 : 1); + + /* + * height in pixels of the wrapped widths + */ + wrappedRepeatHeightPx = wrappedSpaceAboveAlignment; + // add sequences + wrappedRepeatHeightPx += av.getRanges().getViewportHeight() + * charHeight; + // add annotations panel height if shown + wrappedRepeatHeightPx += getAnnotationHeight(); + + /* + * number of visible widths (the last one may be part height), + * ensuring a part height includes at least one sequence + */ + ViewportRanges ranges = av.getRanges(); + wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx; + int remainder = canvasHeight % wrappedRepeatHeightPx; + if (remainder >= (wrappedSpaceAboveAlignment + charHeight)) { - hgap += charHeight; + wrappedVisibleWidths++; } - int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth; - int cHeight = av.getAlignment().getHeight() * charHeight; + /* + * compute width in residues; this also sets East and West label widths + */ + int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth); - av.setWrappedWidth(cWidth); + /* + * limit visibleWidths to not exceed width of alignment + */ + int xMax = ranges.getVisibleAlignmentWidth(); + int startToEnd = xMax - ranges.getStartRes(); + int maxWidths = startToEnd / wrappedWidthInResidues; + if (startToEnd % wrappedWidthInResidues > 0) + { + maxWidths++; + } + wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths); - av.getRanges().setViewportStartAndWidth(startRes, cWidth); + return wrappedWidthInResidues; + } - int endx; - int ypos = hgap; - int maxwidth = av.getAlignment().getWidth(); + /** + * Draws one width of a wrapped alignment, including sequences and + * annnotations, if shown, but not scales or hidden column markers + * + * @param g + * @param ypos + * @param startColumn + * @param endColumn + * @param canvasHeight + */ + protected void drawWrappedWidth(Graphics g, int ypos, int startColumn, + int endColumn, int canvasHeight) + { + ViewportRanges ranges = av.getRanges(); + int viewportWidth = ranges.getViewportWidth(); - if (av.hasHiddenColumns()) + int endx = Math.min(startColumn + viewportWidth - 1, endColumn); + + /* + * move right before drawing by the width of the scale left (if any) + * plus column offset from left margin (usually zero, but may be non-zero + * when fast painting is drawing just a few columns) + */ + int charWidth = av.getCharWidth(); + int xOffset = labelWidthWest + + ((startColumn - ranges.getStartRes()) % viewportWidth) + * charWidth; + g.translate(xOffset, 0); + + // When printing we have an extra clipped region, + // the Printable page which we need to account for here + Shape clip = g.getClip(); + + if (clip == null) { - maxwidth = av.getAlignment().getHiddenColumns() - .findColumnPosition(maxwidth); + g.setClip(0, 0, viewportWidth * charWidth, canvasHeight); + } + else + { + g.setClip(0, (int) clip.getBounds().getY(), + viewportWidth * charWidth, (int) clip.getBounds().getHeight()); } - int annotationHeight = getAnnotationHeight(); + /* + * white fill the region to be drawn (so incremental fast paint doesn't + * scribble over an existing image) + */ + gg.setColor(Color.white); + gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth, + wrappedRepeatHeightPx); - while ((ypos <= canvasHeight) && (startRes < maxwidth)) - { - endx = startRes + cWidth - 1; + drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1, + ypos); - if (endx > maxwidth) + int cHeight = av.getAlignment().getHeight() * av.getCharHeight(); + + if (av.isShowAnnotation()) + { + g.translate(0, cHeight + ypos + 3); + if (annotations == null) { - endx = maxwidth; + annotations = new AnnotationPanel(av); } - g.setFont(av.getFont()); - g.setColor(Color.black); + annotations.renderer.drawComponent(annotations, av, g, -1, + startColumn, endx + 1); + g.translate(0, -cHeight - ypos - 3); + } + g.setClip(clip); + g.translate(-xOffset, 0); + } + + /** + * Draws scales left, right and above (if shown), and any hidden column + * markers, on all widths of the wrapped alignment + * + * @param g + * @param startColumn + */ + protected void drawWrappedDecorators(Graphics g, final int startColumn) + { + int charWidth = av.getCharWidth(); + + g.setFont(av.getFont()); + g.setColor(Color.black); + + int ypos = wrappedSpaceAboveAlignment; + ViewportRanges ranges = av.getRanges(); + int viewportWidth = ranges.getViewportWidth(); + int maxWidth = ranges.getVisibleAlignmentWidth(); + int widthsDrawn = 0; + int startCol = startColumn; + + while (widthsDrawn < wrappedVisibleWidths) + { + int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1); if (av.getScaleLeftWrapped()) { - drawWestScale(g, startRes, endx, ypos); + drawVerticalScale(g, startCol, endColumn - 1, ypos, true); } if (av.getScaleRightWrapped()) { - g.translate(canvasWidth - labelWidthEast, 0); - drawEastScale(g, startRes, endx, ypos); - g.translate(-(canvasWidth - labelWidthEast), 0); + int x = labelWidthWest + viewportWidth * charWidth; + g.translate(x, 0); + drawVerticalScale(g, startCol, endColumn, ypos, false); + g.translate(-x, 0); } + /* + * white fill region of scale above and hidden column markers + * (to support incremental fast paint of image) + */ + g.translate(labelWidthWest, 0); + g.setColor(Color.white); + g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth + * charWidth + labelWidthWest, wrappedSpaceAboveAlignment); + g.setColor(Color.black); + g.translate(-labelWidthWest, 0); + g.translate(labelWidthWest, 0); if (av.getScaleAboveWrapped()) { - drawNorthScale(g, startRes, endx, ypos); + drawNorthScale(g, startCol, endColumn, ypos); } if (av.hasHiddenColumns() && av.getShowHiddenMarkers()) { - g.setColor(Color.blue); - int res; - HiddenColumns hidden = av.getAlignment().getHiddenColumns(); - List positions = hidden.findHiddenRegionPositions(); - for (int pos : positions) - { - res = pos - startRes; - - if (res < 0 || res > endx - startRes) - { - continue; - } - - gg.fillPolygon( - new int[] - { res * charWidth - charHeight / 4, - res * charWidth + charHeight / 4, res * charWidth }, - new int[] - { ypos - (charHeight / 2), ypos - (charHeight / 2), - ypos - (charHeight / 2) + 8 }, - 3); - - } + drawHiddenColumnMarkers(g, ypos, startCol, endColumn); } - // When printing we have an extra clipped region, - // the Printable page which we need to account for here - Shape clip = g.getClip(); + g.translate(-labelWidthWest, 0); - if (clip == null) - { - g.setClip(0, 0, cWidth * charWidth, canvasHeight); - } - else - { - g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth, - (int) clip.getBounds().getHeight()); - } + ypos += wrappedRepeatHeightPx; + startCol += viewportWidth; + widthsDrawn++; + } + } + + /** + * Draws markers (triangles) above hidden column positions between startColumn + * and endColumn. + * + * @param g + * @param ypos + * @param startColumn + * @param endColumn + */ + protected void drawHiddenColumnMarkers(Graphics g, int ypos, + int startColumn, int endColumn) + { + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); - drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos); + g.setColor(Color.blue); + HiddenColumns hidden = av.getAlignment().getHiddenColumns(); + List positions = hidden.findHiddenRegionPositions(); + for (int pos : positions) + { + int res = pos - startColumn; - if (av.isShowAnnotation()) + if (res < 0 || res > endColumn - startColumn + 1) { - g.translate(0, cHeight + ypos + 3); - if (annotations == null) - { - annotations = new AnnotationPanel(av); - } - - annotations.renderer.drawComponent(annotations, av, g, -1, startRes, - endx + 1); - g.translate(0, -cHeight - ypos - 3); + continue; } - g.setClip(clip); - g.translate(-labelWidthWest, 0); - - ypos += cHeight + annotationHeight + hgap; - startRes += cWidth; + /* + * draw a downward-pointing triangle at the hidden columns location + * (before the following visible column) + */ + int xMiddle = res * charWidth; + int[] xPoints = new int[] { xMiddle - charHeight / 4, + xMiddle + charHeight / 4, xMiddle }; + int yTop = ypos - (charHeight / 2); + int[] yPoints = new int[] { yTop, yTop, yTop + 8 }; + g.fillPolygon(xPoints, yPoints, 3); } } @@ -762,6 +912,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int canvasWidth, int canvasHeight, int startRes) { + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + // height gap above each panel int hgap = charHeight; if (av.getScaleAboveWrapped()) @@ -834,22 +987,24 @@ public class SeqCanvas extends JComponent implements ViewportListenerI * marker. * * @param g1 - * Graphics object to draw with + * the graphics context, positioned at the first residue to be drawn * @param startRes - * offset of the first column in the visible region (0..) + * offset of the first column to draw (0..) * @param endRes - * offset of the last column in the visible region (0..) + * offset of the last column to draw (0..) * @param startSeq - * offset of the first sequence in the visible region (0..) + * offset of the first sequence to draw (0..) * @param endSeq - * offset of the last sequence in the visible region (0..) + * offset of the last sequence to draw (0..) * @param yOffset * vertical offset at which to draw (for wrapped alignments) */ public void drawPanel(Graphics g1, final int startRes, final int endRes, final int startSeq, final int endSeq, final int yOffset) { - updateViewport(); + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + if (!av.hasHiddenColumns()) { draw(g1, startRes, endRes, startSeq, endSeq, yOffset); @@ -939,6 +1094,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI private void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq, int offset) { + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + g.setFont(av.getFont()); seqRdr.prepare(g, av.isRenderGaps()); @@ -1118,6 +1276,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group, int startRes, int endRes, int startSeq, int endSeq, int offset) { + int charWidth = av.getCharWidth(); + if (!av.hasHiddenColumns()) { drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq, @@ -1179,6 +1339,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int startRes, int endRes, int startSeq, int endSeq, int verticalOffset) { + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + int visWidth = (endRes - startRes + 1) * charWidth; int oldY = -1; @@ -1347,14 +1510,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI return false; } boolean wrapped = av.getWrapAlignment(); - try { fastPaint = !noFastPaint; fastpainting = fastPaint; - updateViewport(); - /* * to avoid redrawing the whole visible region, we instead * redraw just the minimal regions to remove previous highlights @@ -1498,52 +1658,303 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { fastPaint = true; repaint(); + return; } - else if (av.getWrapAlignment()) + + int scrollX = 0; + if (eventName.equals(ViewportRanges.STARTRES)) { - if (eventName.equals(ViewportRanges.STARTRES)) + // Make sure we're not trying to draw a panel + // larger than the visible window + ViewportRanges vpRanges = av.getRanges(); + scrollX = (int) evt.getNewValue() - (int) evt.getOldValue(); + int range = vpRanges.getViewportWidth(); + if (scrollX > range) { - repaint(); + scrollX = range; + } + else if (scrollX < -range) + { + scrollX = -range; } } - else + + // Both scrolling and resizing change viewport ranges: scrolling changes + // both start and end points, but resize only changes end values. + // Here we only want to fastpaint on a scroll, with resize using a normal + // paint, so scroll events are identified as changes to the horizontal or + // vertical start value. + + // scroll - startres and endres both change + if (eventName.equals(ViewportRanges.STARTRES)) { - int scrollX = 0; - if (eventName.equals(ViewportRanges.STARTRES)) + if (av.getWrapAlignment()) { - // Make sure we're not trying to draw a panel - // larger than the visible window - ViewportRanges vpRanges = av.getRanges(); - scrollX = (int) evt.getNewValue() - (int) evt.getOldValue(); - int range = vpRanges.getEndRes() - vpRanges.getStartRes(); - if (scrollX > range) - { - scrollX = range; - } - else if (scrollX < -range) - { - scrollX = -range; - } + fastPaintWrapped(scrollX); } - - // Both scrolling and resizing change viewport ranges: scrolling changes - // both start and end points, but resize only changes end values. - // Here we only want to fastpaint on a scroll, with resize using a normal - // paint, so scroll events are identified as changes to the horizontal or - // vertical start value. - if (eventName.equals(ViewportRanges.STARTRES)) + else { - // scroll - startres and endres both change fastPaint(scrollX, 0); } - else if (eventName.equals(ViewportRanges.STARTSEQ)) + } + else if (eventName.equals(ViewportRanges.STARTSEQ)) + { + // scroll + fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue()); + } + } + + /** + * Does a minimal update of the image for a scroll movement. This method + * handles scroll movements of up to one width of the wrapped alignment (one + * click in the vertical scrollbar). Larger movements (for example after a + * scroll to highlight a mapped position) trigger a full redraw instead. + * + * @param scrollX + * number of positions scrolled (right if positive, left if negative) + */ + protected void fastPaintWrapped(int scrollX) + { + ViewportRanges ranges = av.getRanges(); + + if (Math.abs(scrollX) > ranges.getViewportWidth()) + { + /* + * shift of more than one view width is + * overcomplicated to handle in this method + */ + fastPaint = false; + repaint(); + return; + } + + if (fastpainting || gg == null) + { + return; + } + + fastPaint = true; + fastpainting = true; + + try + { + calculateWrappedGeometry(getWidth(), getHeight()); + + /* + * relocate the regions of the alignment that are still visible + */ + shiftWrappedAlignment(-scrollX); + + /* + * add new columns (sequence, annotation) + * - at top left if scrollX < 0 + * - at right of last two widths if scrollX > 0 + */ + if (scrollX < 0) + { + int startRes = ranges.getStartRes(); + drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes + - scrollX - 1, getHeight()); + } + else + { + fastPaintWrappedAddRight(scrollX); + } + + /* + * draw all scales (if shown) and hidden column markers + */ + drawWrappedDecorators(gg, ranges.getStartRes()); + + repaint(); + } finally + { + fastpainting = false; + } + } + + /** + * Draws the specified number of columns at the 'end' (bottom right) of a + * wrapped alignment view, including sequences and annotations if shown, but + * not scales. Also draws the same number of columns at the right hand end of + * the second last width shown, if the last width is not full height (so + * cannot simply be copied from the graphics image). + * + * @param columns + */ + protected void fastPaintWrappedAddRight(int columns) + { + if (columns == 0) + { + return; + } + + ViewportRanges ranges = av.getRanges(); + int viewportWidth = ranges.getViewportWidth(); + int charWidth = av.getCharWidth(); + + /** + * draw full height alignment in the second last row, last columns, if the + * last row was not full height + */ + int visibleWidths = wrappedVisibleWidths; + int canvasHeight = getHeight(); + boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight; + + if (lastWidthPartHeight) + { + int widthsAbove = Math.max(0, visibleWidths - 2); + int ypos = wrappedRepeatHeightPx * widthsAbove + + wrappedSpaceAboveAlignment; + int endRes = ranges.getEndRes(); + endRes += widthsAbove * viewportWidth; + int startRes = endRes - columns; + int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth) + * charWidth; + + /* + * white fill first to erase annotations + */ + gg.translate(xOffset, 0); + gg.setColor(Color.white); + gg.fillRect(labelWidthWest, ypos, + (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx); + gg.translate(-xOffset, 0); + + drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight); + } + + /* + * draw newly visible columns in last wrapped width (none if we + * have reached the end of the alignment) + * y-offset for drawing last width is height of widths above, + * plus one gap row + */ + int widthsAbove = visibleWidths - 1; + int ypos = wrappedRepeatHeightPx * widthsAbove + + wrappedSpaceAboveAlignment; + int endRes = ranges.getEndRes(); + endRes += widthsAbove * viewportWidth; + int startRes = endRes - columns + 1; + + /* + * white fill first to erase annotations + */ + int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth) + * charWidth; + gg.translate(xOffset, 0); + gg.setColor(Color.white); + int width = viewportWidth * charWidth - xOffset; + gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx); + gg.translate(-xOffset, 0); + + gg.setFont(av.getFont()); + gg.setColor(Color.black); + + if (startRes < ranges.getVisibleAlignmentWidth()) + { + drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight); + } + + /* + * and finally, white fill any space below the visible alignment + */ + int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx; + if (heightBelow > 0) + { + gg.setColor(Color.white); + gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow); + } + } + + /** + * Shifts the visible alignment by the specified number of columns - left if + * negative, right if positive. Copies and moves sequences and annotations (if + * shown). Scales, hidden column markers and any newly visible columns must be + * drawn separately. + * + * @param positions + */ + protected void shiftWrappedAlignment(int positions) + { + if (positions == 0) + { + return; + } + int charWidth = av.getCharWidth(); + + int canvasHeight = getHeight(); + ViewportRanges ranges = av.getRanges(); + int viewportWidth = ranges.getViewportWidth(); + int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions)) + * charWidth; + int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment; + int xMax = ranges.getVisibleAlignmentWidth(); + + if (positions > 0) + { + /* + * shift right (after scroll left) + * for each wrapped width (starting with the last), copy (width-positions) + * columns from the left margin to the right margin, and copy positions + * columns from the right margin of the row above (if any) to the + * left margin of the current row + */ + + /* + * get y-offset of last wrapped width, first row of sequences + */ + int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx; + y += wrappedSpaceAboveAlignment; + int copyFromLeftStart = labelWidthWest; + int copyFromRightStart = copyFromLeftStart + widthToCopy; + + while (y >= 0) + { + gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy, + positions * charWidth, 0); + if (y > 0) + { + gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx, + positions * charWidth, heightToCopy, -widthToCopy, + wrappedRepeatHeightPx); + } + + y -= wrappedRepeatHeightPx; + } + } + else + { + /* + * shift left (after scroll right) + * for each wrapped width (starting with the first), copy (width-positions) + * columns from the right margin to the left margin, and copy positions + * columns from the left margin of the row below (if any) to the + * right margin of the current row + */ + int xpos = av.getRanges().getStartRes(); + int y = wrappedSpaceAboveAlignment; + int copyFromRightStart = labelWidthWest - positions * charWidth; + + while (y < canvasHeight) { - // scroll - fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue()); + gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy, + positions * charWidth, 0); + if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx + && (xpos + viewportWidth <= xMax)) + { + gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions + * charWidth, heightToCopy, widthToCopy, + -wrappedRepeatHeightPx); + } + + y += wrappedRepeatHeightPx; + xpos += viewportWidth; } } } + /** * Redraws any positions in the search results in the visible region of a * wrapped alignment. Any highlights are drawn depending on the search results @@ -1560,11 +1971,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { return false; } - + int charHeight = av.getCharHeight(); + boolean matchFound = false; + calculateWrappedGeometry(getWidth(), getHeight()); int wrappedWidth = av.getWrappedWidth(); - int wrappedHeight = getRepeatHeightWrapped(); + int wrappedHeight = wrappedRepeatHeightPx; ViewportRanges ranges = av.getRanges(); int canvasHeight = getHeight(); @@ -1663,23 +2076,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } /** - * Answers the height in pixels of a repeating section of the wrapped - * alignment, including space above, scale above if shown, sequences, and - * annotation panel if shown + * Answers the width in pixels of the left scale labels (0 if not shown) * * @return */ - protected int getRepeatHeightWrapped() + int getLabelWidthWest() { - // gap (and maybe scale) above - int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1); - - // add sequences - repeatHeight += av.getRanges().getViewportHeight() * charHeight; - - // add annotations panel height if shown - repeatHeight += getAnnotationHeight(); - - return repeatHeight; + return labelWidthWest; } } diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 516652b..2223ee5 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -215,8 +215,8 @@ public class SeqPanel extends JPanel + hgap + seqCanvas.getAnnotationHeight(); int y = evt.getY(); - y -= hgap; - x = Math.max(0, x - seqCanvas.labelWidthWest); + y = Math.max(0, y - hgap); + x = Math.max(0, x - seqCanvas.getLabelWidthWest()); int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth()); if (cwidth < 1) @@ -719,10 +719,12 @@ public class SeqPanel extends JPanel } /** - * DOCUMENT ME! + * Action on mouse movement is to update the status bar to show the current + * sequence position, and (if features are shown) to show any features at the + * position in a tooltip. Does nothing if the mouse move does not change + * residue position. * * @param evt - * DOCUMENT ME! */ @Override public void mouseMoved(MouseEvent evt) @@ -735,7 +737,8 @@ public class SeqPanel extends JPanel } final int column = findColumn(evt); - int seq = findSeq(evt); + final int seq = findSeq(evt); + if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight()) { lastMouseSeq = -1; @@ -1627,7 +1630,7 @@ public class SeqPanel extends JPanel av.getRanges().scrollRight(true); } - else if (!av.getWrapAlignment()) + else { av.getRanges().scrollUp(false); } @@ -1638,12 +1641,18 @@ public class SeqPanel extends JPanel { av.getRanges().scrollRight(false); } - else if (!av.getWrapAlignment()) + else { av.getRanges().scrollUp(true); } } - // TODO Update tooltip for new position. + + /* + * update status bar and tooltip for new position + * (need to synthesize a mouse movement to refresh tooltip) + */ + mouseMoved(e); + ToolTipManager.sharedInstance().mouseMoved(e); } /** diff --git a/src/jalview/gui/StructureChooser.java b/src/jalview/gui/StructureChooser.java index da10e3f..20f4a49 100644 --- a/src/jalview/gui/StructureChooser.java +++ b/src/jalview/gui/StructureChooser.java @@ -157,8 +157,8 @@ public class StructureChooser extends GStructureChooser Collection wantedFields = pdbDocFieldPrefs .getStructureSummaryFields(); - discoveredStructuresSet = new LinkedHashSet(); - HashSet errors = new HashSet(); + discoveredStructuresSet = new LinkedHashSet<>(); + HashSet errors = new HashSet<>(); for (SequenceI seq : selectedSequences) { FTSRestRequest pdbRequest = new FTSRestRequest(); @@ -223,7 +223,7 @@ public class StructureChooser extends GStructureChooser public void loadLocalCachedPDBEntries() { - ArrayList entries = new ArrayList(); + ArrayList entries = new ArrayList<>(); for (SequenceI seq : selectedSequences) { if (seq.getDatasetSequence() != null @@ -257,7 +257,7 @@ public class StructureChooser extends GStructureChooser boolean isPDBRefsFound = false; boolean isUniProtRefsFound = false; StringBuilder queryBuilder = new StringBuilder(); - Set seqRefs = new LinkedHashSet(); + Set seqRefs = new LinkedHashSet<>(); if (seq.getAllPDBEntries() != null && queryBuilder.length() < MAX_QLENGTH) @@ -401,8 +401,8 @@ public class StructureChooser extends GStructureChooser lbl_loading.setVisible(true); Collection wantedFields = pdbDocFieldPrefs .getStructureSummaryFields(); - Collection filteredResponse = new HashSet(); - HashSet errors = new HashSet(); + Collection filteredResponse = new HashSet<>(); + HashSet errors = new HashSet<>(); for (SequenceI seq : selectedSequences) { @@ -453,7 +453,7 @@ public class StructureChooser extends GStructureChooser if (!filteredResponse.isEmpty()) { final int filterResponseCount = filteredResponse.size(); - Collection reorderedStructuresSet = new LinkedHashSet(); + Collection reorderedStructuresSet = new LinkedHashSet<>(); reorderedStructuresSet.addAll(filteredResponse); reorderedStructuresSet.addAll(discoveredStructuresSet); getResultTable().setModel(FTSRestResponse @@ -725,11 +725,10 @@ public class StructureChooser extends GStructureChooser @Override public void ok_ActionPerformed() { - final long progressSessionId = System.currentTimeMillis(); final StructureSelectionManager ssm = ap.getStructureSelectionManager(); + final int preferredHeight = pnl_filter.getHeight(); - ssm.setProgressIndicator(this); - ssm.setProgressSessionId(progressSessionId); + new Thread(new Runnable() { @Override @@ -747,7 +746,7 @@ public class StructureChooser extends GStructureChooser int[] selectedRows = getResultTable().getSelectedRows(); PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length]; int count = 0; - List selectedSeqsToView = new ArrayList(); + List selectedSeqsToView = new ArrayList<>(); for (int row : selectedRows) { String pdbIdStr = getResultTable() @@ -761,6 +760,7 @@ public class StructureChooser extends GStructureChooser pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries()); } + if (pdbEntry == null) { pdbEntry = new PDBEntry(); @@ -783,7 +783,7 @@ public class StructureChooser extends GStructureChooser .getModelIndex(); int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence") .getModelIndex(); - List selectedSeqsToView = new ArrayList(); + List selectedSeqsToView = new ArrayList<>(); for (int row : selectedRows) { PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row, @@ -805,7 +805,6 @@ public class StructureChooser extends GStructureChooser { selectedSequence = userSelectedSeq; } - String pdbIdStr = txt_search.getText(); PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr); if (pdbEntry == null) @@ -847,6 +846,7 @@ public class StructureChooser extends GStructureChooser { selectedSequence }); } closeAction(preferredHeight); + mainFrame.dispose(); } }).start(); } @@ -870,13 +870,15 @@ public class StructureChooser extends GStructureChooser final PDBEntry[] pdbEntriesToView, final AlignmentPanel alignPanel, SequenceI[] sequences) { - ssm.setProgressBar(MessageManager - .getString("status.launching_3d_structure_viewer")); + long progressId = sequences.hashCode(); + setProgressBar(MessageManager + .getString("status.launching_3d_structure_viewer"), progressId); final StructureViewer sViewer = new StructureViewer(ssm); + setProgressBar(null, progressId); if (SiftsSettings.isMapWithSifts()) { - List seqsWithoutSourceDBRef = new ArrayList(); + List seqsWithoutSourceDBRef = new ArrayList<>(); int p = 0; // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a // real PDB ID. For moment, we can also safely do this if there is already @@ -907,41 +909,43 @@ public class StructureChooser extends GStructureChooser if (!seqsWithoutSourceDBRef.isEmpty()) { int y = seqsWithoutSourceDBRef.size(); - ssm.setProgressBar(null); - ssm.setProgressBar(MessageManager.formatMessage( + setProgressBar(MessageManager.formatMessage( "status.fetching_dbrefs_for_sequences_without_valid_refs", - y)); + y), progressId); SequenceI[] seqWithoutSrcDBRef = new SequenceI[y]; int x = 0; for (SequenceI fSeq : seqsWithoutSourceDBRef) { seqWithoutSrcDBRef[x++] = fSeq; } + DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef); dbRefFetcher.fetchDBRefs(true); + + setProgressBar("Fetch complete.", progressId); // todo i18n } } if (pdbEntriesToView.length > 1) { - ArrayList seqsMap = new ArrayList(); + ArrayList seqsMap = new ArrayList<>(); for (SequenceI seq : sequences) { seqsMap.add(new SequenceI[] { seq }); } SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]); - ssm.setProgressBar(null); - ssm.setProgressBar(MessageManager.getString( - "status.fetching_3d_structures_for_selected_entries")); + + setProgressBar(MessageManager + .getString("status.fetching_3d_structures_for_selected_entries"), progressId); sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel); } else { - ssm.setProgressBar(null); - ssm.setProgressBar(MessageManager.formatMessage( + setProgressBar(MessageManager.formatMessage( "status.fetching_3d_structures_for", - pdbEntriesToView[0].getId())); + pdbEntriesToView[0].getId()),progressId); sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel); } + setProgressBar(null, progressId); } /** @@ -1000,7 +1004,7 @@ public class StructureChooser extends GStructureChooser String searchTerm = txt_search.getText().toLowerCase(); searchTerm = searchTerm.split(":")[0]; // System.out.println(">>>>> search term : " + searchTerm); - List wantedFields = new ArrayList(); + List wantedFields = new ArrayList<>(); FTSRestRequest pdbRequest = new FTSRestRequest(); pdbRequest.setAllowEmptySeq(false); pdbRequest.setResponseSize(1); @@ -1062,7 +1066,7 @@ public class StructureChooser extends GStructureChooser public PDBEntryTableModel(List pdbEntries) { - this.pdbEntries = new ArrayList(pdbEntries); + this.pdbEntries = new ArrayList<>(pdbEntries); } @Override diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java index c8854a7..31c20ed 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -310,6 +310,8 @@ public abstract class StructureViewerBase extends GStructureViewer public abstract ViewerType getViewerType(); + protected abstract IProgressIndicator getIProgressIndicator(); + /** * add a new structure (with associated sequences and chains) to this viewer, * retrieving it if necessary first. @@ -460,7 +462,7 @@ public abstract class StructureViewerBase extends GStructureViewer * create the mappings */ apanel.getStructureSelectionManager().setMapping(seq, chains, - pdbFilename, DataSourceType.FILE); + pdbFilename, DataSourceType.FILE, getIProgressIndicator()); /* * alert the FeatureRenderer to show new (PDB RESNUM) features diff --git a/src/jalview/renderer/ScaleRenderer.java b/src/jalview/renderer/ScaleRenderer.java index 9fec256..d92608c 100644 --- a/src/jalview/renderer/ScaleRenderer.java +++ b/src/jalview/renderer/ScaleRenderer.java @@ -34,12 +34,24 @@ import java.util.List; */ public class ScaleRenderer { + /** + * Represents one major or minor scale mark + */ public final class ScaleMark { + /** + * true for a major scale mark, false for minor + */ public final boolean major; + /** + * visible column position (0..) e.g. 19 + */ public final int column; + /** + * text (if any) to show e.g. "20" + */ public final String text; ScaleMark(boolean isMajor, int col, String txt) @@ -48,19 +60,27 @@ public class ScaleRenderer column = col; text = txt; } + + /** + * String representation for inspection when debugging only + */ + @Override + public String toString() + { + return String.format("%s:%d:%s", major ? "major" : "minor", column, + text); + } } /** - * calculate positions markers on the alignment ruler + * Calculates position markers on the alignment ruler * * @param av * @param startx - * left-most column in visible view + * left-most column in visible view (0..) * @param endx - * - right-most column in visible view - * @return List of ScaleMark holding boolean: true/false for major/minor mark, - * marker position in alignment column coords, a String to be rendered - * at the position (or null) + * - right-most column in visible view (0..) + * @return */ public List calculateMarks(AlignViewportI av, int startx, int endx) @@ -87,41 +107,40 @@ public class ScaleRenderer scalestartx += 5; } List marks = new ArrayList(); - String string; - int refN, iadj; // todo: add a 'reference origin column' to set column number relative to - for (int i = scalestartx; i < endx; i += 5) + for (int i = scalestartx; i <= endx; i += 5) { if (((i - refSp) % 10) == 0) { + String text; if (refSeq == null) { - iadj = av.getAlignment().getHiddenColumns() + int iadj = av.getAlignment().getHiddenColumns() .adjustForHiddenColumns(i - 1) + 1; - string = String.valueOf(iadj); + text = String.valueOf(iadj); } else { - iadj = av.getAlignment().getHiddenColumns() + int iadj = av.getAlignment().getHiddenColumns() .adjustForHiddenColumns(i - 1); - refN = refSeq.findPosition(iadj); + int refN = refSeq.findPosition(iadj); // TODO show bounds if position is a gap // - ie L--R -> "1L|2R" for // marker if (iadj < refStartI) { - string = String.valueOf(iadj - refStartI); + text = String.valueOf(iadj - refStartI); } else if (iadj > refEndI) { - string = "+" + String.valueOf(iadj - refEndI); + text = "+" + String.valueOf(iadj - refEndI); } else { - string = String.valueOf(refN) + refSeq.getCharAt(iadj); + text = String.valueOf(refN) + refSeq.getCharAt(iadj); } } - marks.add(new ScaleMark(true, i - startx - 1, string)); + marks.add(new ScaleMark(true, i - startx - 1, text)); } else { diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index b973f45..35e2536 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -66,7 +66,7 @@ public class StructureSelectionManager static IdentityHashMap instances; - private List mappings = new ArrayList(); + private List mappings = new ArrayList<>(); private boolean processSecondaryStructure = false; @@ -74,20 +74,16 @@ public class StructureSelectionManager private boolean addTempFacAnnot = false; - private IProgressIndicator progressIndicator; - private SiftsClient siftsClient = null; - private long progressSessionId; - /* * Set of any registered mappings between (dataset) sequences. */ - private List seqmappings = new ArrayList(); + private List seqmappings = new ArrayList<>(); - private List commandListeners = new ArrayList(); + private List commandListeners = new ArrayList<>(); - private List sel_listeners = new ArrayList(); + private List sel_listeners = new ArrayList<>(); /** * @return true if will try to use external services for processing secondary @@ -175,9 +171,9 @@ public class StructureSelectionManager * map between the PDB IDs (or structure identifiers) used by Jalview and the * absolute filenames for PDB data that corresponds to it */ - Map pdbIdFileName = new HashMap(); + Map pdbIdFileName = new HashMap<>(); - Map pdbFileNameId = new HashMap(); + Map pdbFileNameId = new HashMap<>(); public void registerPDBFile(String idForFile, String absoluteFile) { @@ -228,7 +224,7 @@ public class StructureSelectionManager } if (instances == null) { - instances = new java.util.IdentityHashMap(); + instances = new java.util.IdentityHashMap<>(); } StructureSelectionManager instance = instances.get(context); if (instance == null) @@ -324,9 +320,11 @@ public class StructureSelectionManager * @return null or the structure data parsed as a pdb file */ synchronized public StructureFile setMapping(SequenceI[] sequence, - String[] targetChains, String pdbFile, DataSourceType protocol) + String[] targetChains, String pdbFile, DataSourceType protocol, + IProgressIndicator progress) { - return setMapping(true, sequence, targetChains, pdbFile, protocol); + return computeMapping(true, sequence, targetChains, pdbFile, protocol, + progress); } /** @@ -353,6 +351,16 @@ public class StructureSelectionManager SequenceI[] sequenceArray, String[] targetChainIds, String pdbFile, DataSourceType sourceType) { + return computeMapping(forStructureView, sequenceArray, targetChainIds, + pdbFile, sourceType, null); + } + + synchronized public StructureFile computeMapping( + boolean forStructureView, SequenceI[] sequenceArray, + String[] targetChainIds, String pdbFile, DataSourceType sourceType, + IProgressIndicator progress) + { + long progressSessionId = System.currentTimeMillis() * 3; /* * There will be better ways of doing this in the future, for now we'll use * the tried and tested MCview pdb mapping @@ -500,12 +508,14 @@ public class StructureSelectionManager pdbFile = "INLINE" + pdb.getId(); } - List seqToStrucMapping = new ArrayList(); + List seqToStrucMapping = new ArrayList<>(); if (isMapUsingSIFTs && seq.isProtein()) { - setProgressBar(null); - setProgressBar(MessageManager - .getString("status.obtaining_mapping_with_sifts")); + if (progress!=null) { + progress.setProgressBar(MessageManager + .getString("status.obtaining_mapping_with_sifts"), + progressSessionId); + } jalview.datamodel.Mapping sqmpping = maxAlignseq .getMappingFromS1(false); if (targetChainId != null && !targetChainId.trim().isEmpty()) @@ -538,7 +548,7 @@ public class StructureSelectionManager } else { - List foundSiftsMappings = new ArrayList(); + List foundSiftsMappings = new ArrayList<>(); for (PDBChain chain : pdb.getChains()) { try @@ -575,20 +585,25 @@ public class StructureSelectionManager } else { - setProgressBar(null); - setProgressBar(MessageManager - .getString("status.obtaining_mapping_with_nw_alignment")); + if (progress != null) + { + progress.setProgressBar(MessageManager + .getString("status.obtaining_mapping_with_nw_alignment"), + progressSessionId); + } StructureMapping nwMapping = getNWMappings(seq, pdbFile, maxChainId, maxChain, pdb, maxAlignseq); seqToStrucMapping.add(nwMapping); ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0)); - } - if (forStructureView) { mappings.addAll(seqToStrucMapping); } + if (progress != null) + { + progress.setProgressBar(null, progressSessionId); + } } return pdb; } @@ -683,7 +698,7 @@ public class StructureSelectionManager .getMappingFromS1(false); maxChain.transferRESNUMFeatures(seq, null); - HashMap mapping = new HashMap(); + HashMap mapping = new HashMap<>(); int resNum = -10000; int index = 0; char insCode = ' '; @@ -737,7 +752,7 @@ public class StructureSelectionManager * Remove mappings to the closed listener's PDB files, but first check if * another listener is still interested */ - List pdbs = new ArrayList(Arrays.asList(pdbfiles)); + List pdbs = new ArrayList<>(Arrays.asList(pdbfiles)); StructureListener sl; for (int i = 0; i < listeners.size(); i++) @@ -758,7 +773,7 @@ public class StructureSelectionManager */ if (pdbs.size() > 0) { - List tmp = new ArrayList(); + List tmp = new ArrayList<>(); for (StructureMapping sm : mappings) { if (!pdbs.contains(sm.pdbfile)) @@ -844,7 +859,7 @@ public class StructureSelectionManager && sm.pdbchain.equals(atom.getChain())) { int indexpos = sm.getSeqPos(atom.getPdbResNum()); - if (lastipos != indexpos && lastseq != sm.sequence) + if (lastipos != indexpos || lastseq != sm.sequence) { results.addResult(sm.sequence, indexpos, indexpos); lastipos = indexpos; @@ -952,7 +967,7 @@ public class StructureSelectionManager return; } int atomNo; - List atoms = new ArrayList(); + List atoms = new ArrayList<>(); for (StructureMapping sm : mappings) { if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence() @@ -1060,7 +1075,7 @@ public class StructureSelectionManager public StructureMapping[] getMapping(String pdbfile) { - List tmp = new ArrayList(); + List tmp = new ArrayList<>(); for (StructureMapping sm : mappings) { if (sm.pdbfile.equals(pdbfile)) @@ -1220,7 +1235,7 @@ public class StructureSelectionManager } } - Vector view_listeners = new Vector(); + Vector view_listeners = new Vector<>(); public synchronized void sendViewPosition( jalview.api.AlignmentViewPanel source, int startRes, int endRes, @@ -1343,35 +1358,6 @@ public class StructureSelectionManager return null; } - public IProgressIndicator getProgressIndicator() - { - return progressIndicator; - } - - public void setProgressIndicator(IProgressIndicator progressIndicator) - { - this.progressIndicator = progressIndicator; - } - - public long getProgressSessionId() - { - return progressSessionId; - } - - public void setProgressSessionId(long progressSessionId) - { - this.progressSessionId = progressSessionId; - } - - public void setProgressBar(String message) - { - if (progressIndicator == null) - { - return; - } - progressIndicator.setProgressBar(message, progressSessionId); - } - public List getSequenceMappings() { return seqmappings; diff --git a/src/jalview/util/MappingUtils.java b/src/jalview/util/MappingUtils.java index 3682239..9c5c109 100644 --- a/src/jalview/util/MappingUtils.java +++ b/src/jalview/util/MappingUtils.java @@ -939,4 +939,55 @@ public final class MappingUtils } return copy; } + + /** + * Removes the specified number of positions from the given ranges. Provided + * to allow a stop codon to be stripped from a CDS sequence so that it matches + * the peptide translation length. + * + * @param positions + * @param ranges + * a list of (single) [start, end] ranges + * @return + */ + public static void removeEndPositions(int positions, + List ranges) + { + int toRemove = positions; + Iterator it = new ReverseListIterator<>(ranges); + while (toRemove > 0) + { + int[] endRange = it.next(); + if (endRange.length != 2) + { + /* + * not coded for [start1, end1, start2, end2, ...] + */ + System.err + .println("MappingUtils.removeEndPositions doesn't handle multiple ranges"); + return; + } + + int length = endRange[1] - endRange[0] + 1; + if (length <= 0) + { + /* + * not coded for a reverse strand range (end < start) + */ + System.err + .println("MappingUtils.removeEndPositions doesn't handle reverse strand"); + return; + } + if (length > toRemove) + { + endRange[1] -= toRemove; + toRemove = 0; + } + else + { + toRemove -= length; + it.remove(); + } + } + } } diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index b260cab..fdad0ce 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -22,6 +22,7 @@ package jalview.viewmodel; import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; import jalview.analysis.Conservation; +import jalview.analysis.TreeModel; import jalview.api.AlignCalcManagerI; import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; @@ -79,7 +80,7 @@ import java.util.Map; public abstract class AlignmentViewport implements AlignViewportI, CommandListener, VamsasSource { - final protected ViewportRanges ranges; + protected ViewportRanges ranges; protected ViewStyleI viewStyle = new ViewStyle(); @@ -947,11 +948,15 @@ public abstract class AlignmentViewport groupConsensus = null; groupConservation = null; hconsensus = null; + hconservation = null; hcomplementConsensus = null; - // colour scheme may hold reference to consensus - residueShading = null; - // TODO remove listeners from changeSupport? + gapcounts = null; + calculator = null; + residueShading = null; // may hold a reference to Consensus changeSupport = null; + ranges = null; + currentTree = null; + selectionGroup = null; setAlignment(null); } @@ -2869,6 +2874,8 @@ public abstract class AlignmentViewport */ private SearchResultsI searchResults = null; + protected TreeModel currentTree = null; + @Override public boolean hasSearchResults() { @@ -2927,4 +2934,16 @@ public abstract class AlignmentViewport + ((ignoreGapsInConsensusCalculation) ? " without gaps" : "")); return sq; } + + @Override + public void setCurrentTree(TreeModel tree) + { + currentTree = tree; + } + + @Override + public TreeModel getCurrentTree() + { + return currentTree; + } } diff --git a/src/jalview/viewmodel/ViewportRanges.java b/src/jalview/viewmodel/ViewportRanges.java index 42d490e..24ff57f 100644 --- a/src/jalview/viewmodel/ViewportRanges.java +++ b/src/jalview/viewmodel/ViewportRanges.java @@ -402,23 +402,39 @@ public class ViewportRanges extends ViewportProperties */ public boolean scrollUp(boolean up) { + /* + * if in unwrapped mode, scroll up or down one sequence row; + * if in wrapped mode, scroll by one visible width of columns + */ if (up) { - if (startSeq < 1) + if (wrappedMode) { - return false; + pageUp(); + } + else + { + if (startSeq < 1) + { + return false; + } + setStartSeq(startSeq - 1); } - - setStartSeq(startSeq - 1); } else { - if (endSeq >= getVisibleAlignmentHeight() - 1) + if (wrappedMode) { - return false; + pageDown(); + } + else + { + if (endSeq >= getVisibleAlignmentHeight() - 1) + { + return false; + } + setStartSeq(startSeq + 1); } - - setStartSeq(startSeq + 1); } return true; } diff --git a/src/jalview/ws/jws2/AbstractJabaCalcWorker.java b/src/jalview/ws/jws2/AbstractJabaCalcWorker.java index b972ab8..dd64e77 100644 --- a/src/jalview/ws/jws2/AbstractJabaCalcWorker.java +++ b/src/jalview/ws/jws2/AbstractJabaCalcWorker.java @@ -30,6 +30,7 @@ import jalview.datamodel.AnnotatedCollectionI; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.IProgressIndicator; +import jalview.gui.IProgressIndicatorHandler; import jalview.schemes.ResidueProperties; import jalview.workers.AlignCalcWorker; import jalview.ws.jws2.dm.AAConSettings; @@ -220,7 +221,26 @@ public abstract class AbstractJabaCalcWorker extends AlignCalcWorker progressId = System.currentTimeMillis()); } rslt = submitToService(seqs); + if (guiProgress != null) + { + guiProgress.registerHandler(progressId, + new IProgressIndicatorHandler() + { + @Override + public boolean cancelActivity(long id) + { + cancelCurrentJob(); + return true; + } + + @Override + public boolean canCancel() + { + return true; + } + }); + } boolean finished = false; long rpos = 0; do diff --git a/test/jalview/analysis/AlignmentGenerator.java b/test/jalview/analysis/AlignmentGenerator.java index 3187fd9..9d3877c 100644 --- a/test/jalview/analysis/AlignmentGenerator.java +++ b/test/jalview/analysis/AlignmentGenerator.java @@ -27,39 +27,25 @@ import jalview.datamodel.SequenceI; import jalview.gui.JvOptionPane; import jalview.io.FastaFile; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.Arrays; import java.util.Random; import org.testng.annotations.BeforeClass; /** - * Generates, and outputs in Fasta format, a random DNA alignment for given + * Generates, and outputs in Fasta format, a random peptide or nucleotide alignment for given * sequence length and count. Will regenerate the same alignment each time if * the same random seed is used (so may be used for reproducible unit tests). * Not guaranteed to reproduce the same results between versions, as the rules * may get tweaked to produce more 'realistic' results. * - * Arguments: - *
      - *
    • length (number of bases in each sequence)
    • - *
    • height (number of sequences)
    • - *
    • a whole number random seed
    • - *
    • percentage of gaps to include (0-100)
    • - *
    • percentage chance of variation of each position (0-100)
    • - *
    - * * @author gmcarstairs - * */ public class AlignmentGenerator { - @BeforeClass(alwaysRun = true) - public void setUpJvOptionPane() - { - JvOptionPane.setInteractiveMode(false); - JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); - } - private static final char GAP = '-'; private static final char ZERO = '0'; @@ -72,51 +58,76 @@ public class AlignmentGenerator private Random random; + private PrintStream ps; /** - * Outputs a DNA 'alignment' where each position is a random choice from - * 'GTCA-'. + * Outputs a pseudo-randomly generated nucleotide or peptide alignment + * Arguments: + *
      + *
    • n (for nucleotide) or p (for peptide)
    • + *
    • length (number of bases in each sequence)
    • + *
    • height (number of sequences)
    • + *
    • a whole number random seed
    • + *
    • percentage of gaps to include (0-100)
    • + *
    • percentage chance of variation of each position (0-100)
    • + *
    • (optional) path to a file to write the alignment to
    • + *
    + * * * @param args + * @throws FileNotFoundException */ - public static void main(String[] args) + public static void main(String[] args) throws FileNotFoundException { - if (args.length != 6) + if (args.length != 6 && args.length != 7) { usage(); return; } + + PrintStream ps = System.out; + if (args.length == 7) + { + ps = new PrintStream(new File(args[6])); + } + boolean nucleotide = args[0].toLowerCase().startsWith("n"); int width = Integer.parseInt(args[1]); int height = Integer.parseInt(args[2]); long randomSeed = Long.valueOf(args[3]); int gapPercentage = Integer.valueOf(args[4]); int changePercentage = Integer.valueOf(args[5]); - AlignmentI al = new AlignmentGenerator(nucleotide).generate(width, - height, - randomSeed, gapPercentage, changePercentage); - System.out.println("; " + height + " sequences of " + width + ps.println("; " + height + " sequences of " + width + " bases with " + gapPercentage + "% gaps and " + changePercentage + "% mutations (random seed = " + randomSeed + ")"); - System.out.println(new FastaFile().print(al.getSequencesArray(), true)); + + new AlignmentGenerator(nucleotide, ps).generate(width, height, + randomSeed, gapPercentage, changePercentage); + + if (ps != System.out) + { + ps.close(); + } } /** - * Print parameter help. + * Prints parameter help */ private static void usage() { System.out.println("Usage:"); System.out.println("arg0: n (for nucleotide) or p (for peptide)"); System.out.println("arg1: number of (non-gap) bases per sequence"); - System.out.println("arg2: number sequences"); + System.out.println("arg2: number of sequences"); System.out .println("arg3: an integer as random seed (same seed = same results)"); System.out.println("arg4: percentage of gaps to (randomly) generate"); System.out .println("arg5: percentage of 'mutations' to (randomly) generate"); + System.out + .println("arg6: (optional) path to output file (default is sysout)"); System.out.println("Example: AlignmentGenerator n 12 15 387 10 5"); System.out .println("- 15 nucleotide sequences of 12 bases each, approx 10% gaps and 5% mutations, random seed = 387"); @@ -124,16 +135,28 @@ public class AlignmentGenerator } /** - * Constructor that sets nucleotide or peptide symbol set + * Constructor that sets nucleotide or peptide symbol set, and also writes the + * generated alignment to sysout */ public AlignmentGenerator(boolean nuc) { - BASES = nuc ? NUCS : PEPS; + this(nuc, System.out); + } + + /** + * Constructor that sets nucleotide or peptide symbol set, and also writes the + * generated alignment to the specified output stream (if not null). This can + * be used to write the alignment to a file or sysout. + */ + public AlignmentGenerator(boolean nucleotide, PrintStream printStream) + { + BASES = nucleotide ? NUCS : PEPS; + ps = printStream; } /** - * Outputs a DNA 'alignment' of given width and height, where each position is - * a random choice from 'GTCA-'. + * Outputs an 'alignment' of given width and height, where each position is a + * random choice from the symbol alphabet, or - for gap * * @param width * @param height @@ -153,6 +176,12 @@ public class AlignmentGenerator seqno + 1, width, changePercentage); } AlignmentI al = new Alignment(seqs); + + if (ps != null) + { + ps.println(new FastaFile().print(al.getSequencesArray(), true)); + } + return al; } diff --git a/test/jalview/analysis/AlignmentUtilsTests.java b/test/jalview/analysis/AlignmentUtilsTests.java index 4439bb9..06b51e6 100644 --- a/test/jalview/analysis/AlignmentUtilsTests.java +++ b/test/jalview/analysis/AlignmentUtilsTests.java @@ -2533,4 +2533,71 @@ public class AlignmentUtilsTests assertEquals(s_as3, uas3.getSequenceAsString()); } + /** + * Tests for the method that maps nucleotide to protein based on CDS features + */ + @Test(groups = "Functional") + public void testMapCdsToProtein() + { + SequenceI peptide = new Sequence("pep", "KLQ"); + + /* + * Case 1: CDS 3 times length of peptide + * NB method only checks lengths match, not translation + */ + SequenceI dna = new Sequence("dna", "AACGacgtCTCCT"); + dna.createDatasetSequence(); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null)); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 13, null)); + MapList ml = AlignmentUtils.mapCdsToProtein(dna, peptide); + assertEquals(3, ml.getFromRatio()); + assertEquals(1, ml.getToRatio()); + assertEquals("[[1, 3]]", + Arrays.deepToString(ml.getToRanges().toArray())); + assertEquals("[[1, 4], [9, 13]]", + Arrays.deepToString(ml.getFromRanges().toArray())); + + /* + * Case 2: CDS 3 times length of peptide + stop codon + * (note code does not currently check trailing codon is a stop codon) + */ + dna = new Sequence("dna", "AACGacgtCTCCTTGA"); + dna.createDatasetSequence(); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null)); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 16, null)); + ml = AlignmentUtils.mapCdsToProtein(dna, peptide); + assertEquals(3, ml.getFromRatio()); + assertEquals(1, ml.getToRatio()); + assertEquals("[[1, 3]]", + Arrays.deepToString(ml.getToRanges().toArray())); + assertEquals("[[1, 4], [9, 13]]", + Arrays.deepToString(ml.getFromRanges().toArray())); + + /* + * Case 3: CDS not 3 times length of peptide - no mapping is made + */ + dna = new Sequence("dna", "AACGacgtCTCCTTG"); + dna.createDatasetSequence(); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null)); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 15, null)); + ml = AlignmentUtils.mapCdsToProtein(dna, peptide); + assertNull(ml); + + /* + * Case 4: incomplete start codon corresponding to X in peptide + */ + dna = new Sequence("dna", "ACGacgtCTCCTTGG"); + dna.createDatasetSequence(); + SequenceFeature sf = new SequenceFeature("CDS", "", 1, 3, null); + sf.setPhase("2"); // skip 2 positions (AC) to start of next codon (GCT) + dna.addSequenceFeature(sf); + dna.addSequenceFeature(new SequenceFeature("CDS", "", 8, 15, null)); + peptide = new Sequence("pep", "XLQ"); + ml = AlignmentUtils.mapCdsToProtein(dna, peptide); + assertEquals("[[2, 3]]", + Arrays.deepToString(ml.getToRanges().toArray())); + assertEquals("[[3, 3], [8, 12]]", + Arrays.deepToString(ml.getFromRanges().toArray())); + } + } diff --git a/test/jalview/analysis/TestAlignSeq.java b/test/jalview/analysis/TestAlignSeq.java index 70e59c5..e2e5594 100644 --- a/test/jalview/analysis/TestAlignSeq.java +++ b/test/jalview/analysis/TestAlignSeq.java @@ -64,7 +64,7 @@ public class TestAlignSeq s2 = new Sequence("Seq2", "ASDFA"); s2.setStart(5); s2.setEnd(9); - s3 = new Sequence("Seq1", "SDFAQQQSSS"); + s3 = new Sequence("Seq3", "SDFAQQQSSS"); } @@ -125,10 +125,10 @@ public class TestAlignSeq }; as.printAlignment(ps); - String expected = "Score = 320.0\nLength of alignment = 10\nSequence Seq1 : 3 - 18 (Sequence length = 14)\nSequence Seq1 : 1 - 10 (Sequence length = 10)\n\n" - + "Seq1 SDFAQQQRRR\n" - + " ||||||| \n" - + "Seq1 SDFAQQQSSS\n\n" + "Percentage ID = 70.00\n"; + String expected = "Score = 320.0\nLength of alignment = 10\nSequence Seq1/4-13 (Sequence length = 14)\nSequence Seq3/1-10 (Sequence length = 10)\n\n" + + "Seq1/4-13 SDFAQQQRRR\n" + + " ||||||| \n" + + "Seq3/1-10 SDFAQQQSSS\n\n" + "Percentage ID = 70.00\n\n"; assertEquals(expected, baos.toString()); } } diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index 23e8cf7..c0cb09c 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -1704,4 +1704,103 @@ public class SequenceTest found = sq.findFeatures(10, 11); assertEquals(0, found.size()); } + + @Test(groups = { "Functional" }) + public void testSetName() + { + SequenceI sq = new Sequence("test", "-ABC---DE-F--"); + assertEquals("test", sq.getName()); + assertEquals(1, sq.getStart()); + assertEquals(6, sq.getEnd()); + + sq.setName("testing"); + assertEquals("testing", sq.getName()); + + sq.setName("test/8-10"); + assertEquals("test", sq.getName()); + assertEquals(8, sq.getStart()); + assertEquals(13, sq.getEnd()); // note end is recomputed + + sq.setName("testing/7-99"); + assertEquals("testing", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); // end may be beyond physical end + + sq.setName("/2-3"); + assertEquals("", sq.getName()); + assertEquals(2, sq.getStart()); + assertEquals(7, sq.getEnd()); + + sq.setName("test/"); // invalid + assertEquals("test/", sq.getName()); + assertEquals(2, sq.getStart()); + assertEquals(7, sq.getEnd()); + + sq.setName("test/6-13/7-99"); + assertEquals("test/6-13", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName("test/0-5"); // 0 is invalid - ignored + assertEquals("test/0-5", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName("test/a-5"); // a is invalid - ignored + assertEquals("test/a-5", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName("test/6-5"); // start > end is invalid - ignored + assertEquals("test/6-5", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName("test/5"); // invalid - ignored + assertEquals("test/5", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName("test/-5"); // invalid - ignored + assertEquals("test/-5", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName("test/5-"); // invalid - ignored + assertEquals("test/5-", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName("test/5-6-7"); // invalid - ignored + assertEquals("test/5-6-7", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + + sq.setName(null); // invalid, gets converted to space + assertEquals("", sq.getName()); + assertEquals(7, sq.getStart()); + assertEquals(99, sq.getEnd()); + } + + @Test(groups = { "Functional" }) + public void testCheckValidRange() + { + Sequence sq = new Sequence("test/7-12", "-ABC---DE-F--"); + assertEquals(7, sq.getStart()); + assertEquals(12, sq.getEnd()); + + /* + * checkValidRange ensures end is at least the last residue position + */ + PA.setValue(sq, "end", 2); + sq.checkValidRange(); + assertEquals(12, sq.getEnd()); + + /* + * end may be beyond the last residue position + */ + PA.setValue(sq, "end", 22); + sq.checkValidRange(); + assertEquals(22, sq.getEnd()); + } } diff --git a/test/jalview/ext/ensembl/EnsemblGeneTest.java b/test/jalview/ext/ensembl/EnsemblGeneTest.java index a8c491c..5920b89 100644 --- a/test/jalview/ext/ensembl/EnsemblGeneTest.java +++ b/test/jalview/ext/ensembl/EnsemblGeneTest.java @@ -296,4 +296,28 @@ public class EnsemblGeneTest assertEquals(-1, fc.compare("coding_exon", "feature_variant")); assertEquals(1f, fc.getTransparency()); } + + @Test(groups = "Network") + public void testGetGeneIds() + { + /* + * ENSG00000158828 gene id PINK1 human + * ENST00000321556 transcript for the same gene - should not be duplicated + * P30419 Uniprot identifier for ENSG00000136448 + * ENST00000592782 transcript for Uniprot gene - should not be duplicated + * BRAF - gene name resolvabe (at time of writing) for 6 model species + */ + String ids = "ENSG00000158828 ENST00000321556 P30419 ENST00000592782 BRAF"; + EnsemblGene testee = new EnsemblGene(); + List geneIds = testee.getGeneIds(ids); + assertEquals(8, geneIds.size()); + assertTrue(geneIds.contains("ENSG00000158828")); + assertTrue(geneIds.contains("ENSG00000136448")); + assertTrue(geneIds.contains("ENSG00000157764")); // BRAF human + assertTrue(geneIds.contains("ENSMUSG00000002413")); // mouse + assertTrue(geneIds.contains("ENSRNOG00000010957")); // rat + assertTrue(geneIds.contains("ENSXETG00000004845")); // xenopus + assertTrue(geneIds.contains("ENSDARG00000017661")); // zebrafish + assertTrue(geneIds.contains("ENSGALG00000012865")); // chicken + } } diff --git a/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java b/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java index aa2c315..e2af26b 100644 --- a/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java +++ b/test/jalview/ext/ensembl/EnsemblSeqProxyTest.java @@ -191,34 +191,6 @@ public class EnsemblSeqProxyTest } - @Test(groups = "Functional") - public void testIsTranscriptIdentifier() - { - EnsemblSeqProxy testee = new EnsemblGene(); - assertFalse(testee.isTranscriptIdentifier(null)); - assertFalse(testee.isTranscriptIdentifier("")); - assertFalse(testee.isTranscriptIdentifier("ENSG00000012345")); - assertTrue(testee.isTranscriptIdentifier("ENST00000012345")); - assertTrue(testee.isTranscriptIdentifier("ENSMUST00000012345")); - assertFalse(testee.isTranscriptIdentifier("enst00000012345")); - assertFalse(testee.isTranscriptIdentifier("ENST000000123456")); - assertFalse(testee.isTranscriptIdentifier("ENST0000001234")); - } - - @Test(groups = "Functional") - public void testIsGeneIdentifier() - { - EnsemblSeqProxy testee = new EnsemblGene(); - assertFalse(testee.isGeneIdentifier(null)); - assertFalse(testee.isGeneIdentifier("")); - assertFalse(testee.isGeneIdentifier("ENST00000012345")); - assertTrue(testee.isGeneIdentifier("ENSG00000012345")); - assertTrue(testee.isGeneIdentifier("ENSMUSG00000012345")); - assertFalse(testee.isGeneIdentifier("ensg00000012345")); - assertFalse(testee.isGeneIdentifier("ENSG000000123456")); - assertFalse(testee.isGeneIdentifier("ENSG0000001234")); - } - /** * Test the method that appends a single allele's reverse complement to a * string buffer diff --git a/test/jalview/gui/AlignmentPanelTest.java b/test/jalview/gui/AlignmentPanelTest.java index b228ba1..2819dbf 100644 --- a/test/jalview/gui/AlignmentPanelTest.java +++ b/test/jalview/gui/AlignmentPanelTest.java @@ -21,6 +21,7 @@ package jalview.gui; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotEquals; import jalview.bin.Cache; import jalview.bin.Jalview; @@ -218,4 +219,31 @@ public class AlignmentPanelTest .getAlignment().getWidth() - 1 - 21); // 21 is the number of hidden // columns } + + /** + * Test that update layout reverts to original (unwrapped) values for endRes + * and endSeq when switching from wrapped to unwrapped mode (JAL-2739) + */ + @Test(groups = "Functional") + public void TestUpdateLayout_endRes() + { + // get details of original alignment dimensions + ViewportRanges ranges = af.getViewport().getRanges(); + int endres = ranges.getEndRes(); + + // wrap + af.alignPanel.getAlignViewport().setWrapAlignment(true); + af.alignPanel.updateLayout(); + + // endRes changes + assertNotEquals(ranges.getEndRes(), endres); + + // unwrap + af.alignPanel.getAlignViewport().setWrapAlignment(false); + af.alignPanel.updateLayout(); + + // endRes and endSeq back to original values + assertEquals(ranges.getEndRes(), endres); + + } } diff --git a/test/jalview/gui/FreeUpMemoryTest.java b/test/jalview/gui/FreeUpMemoryTest.java new file mode 100644 index 0000000..e93bfac --- /dev/null +++ b/test/jalview/gui/FreeUpMemoryTest.java @@ -0,0 +1,216 @@ +package jalview.gui; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import jalview.analysis.AlignmentGenerator; +import jalview.bin.Cache; +import jalview.bin.Jalview; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceGroup; +import jalview.io.DataSourceType; +import jalview.io.FileLoader; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class FreeUpMemoryTest +{ + private static final int ONE_MB = 1000 * 1000; + + /** + * Configure (read-only) Jalview property settings for test + */ + @BeforeClass(alwaysRun = true) + public void setUp() + { + Jalview.main(new String[] { "-nonews", "-props", + "test/jalview/testProps.jvprops" }); + Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", + Boolean.TRUE.toString()); + Cache.applicationProperties.setProperty("SHOW_QUALITY", + Boolean.TRUE.toString()); + Cache.applicationProperties.setProperty("SHOW_CONSERVATION", + Boolean.TRUE.toString()); + Cache.applicationProperties.setProperty("SHOW_OCCUPANCY", + Boolean.TRUE.toString()); + Cache.applicationProperties.setProperty("SHOW_IDENTITY", + Boolean.TRUE.toString()); + } + + /** + * A simple test that memory is released when all windows are closed. + *
      + *
    • generates a reasonably large alignment and loads it
    • + *
    • performs various operations on the alignment
    • + *
    • closes all windows
    • + *
    • requests garbage collection
    • + *
    • asserts that the remaining memory footprint (heap usage) is 'not large' + *
    • + *
    + * If the test fails, this suggests that a reference to some large object + * (perhaps the alignment data, or some annotation / Tree / PCA data) has + * failed to be garbage collected. If this is the case, the heap will need to + * be inspected manually (suggest using jvisualvm) in order to track down + * where large objects are still referenced. The code (for example + * AlignmentViewport.dispose()) should then be updated to ensure references to + * large objects are set to null when they are no longer required. + * + * @throws IOException + */ + @Test(groups = "Memory") + public void testFreeMemoryOnClose() throws IOException + { + File f = generateAlignment(); + f.deleteOnExit(); + + doStuffInJalview(f); + + Desktop.instance.closeAll_actionPerformed(null); + + checkUsedMemory(35L); + } + + /** + * Requests garbage collection and then checks whether remaining memory in use + * is less than the expected value (in Megabytes) + * + * @param expectedMax + */ + protected void checkUsedMemory(long expectedMax) + { + /* + * request garbage collection and wait briefly for it to run; + * NB there is no guarantee when, or whether, it will do so + */ + System.gc(); + waitFor(100); + + /* + * a second gc() call should not be necessary - but it is! + * the test passes with it, and fails without it + */ + System.gc(); + waitFor(100); + + /* + * check used memory is 'reasonably low' + */ + long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB; + long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB; + long usedMemory = availableMemory - freeMemory; + + /* + * sanity check - fails if any frame was added after + * closeAll_actionPerformed + */ + assertEquals(Desktop.instance.getAllFrames().length, 0); + + /* + * if this assertion fails + * - set a breakpoint here + * - run jvisualvm to inspect a heap dump of Jalview + * - identify large objects in the heap and their referers + * - fix code as necessary to null the references on close + */ + System.out.println("Used memory after gc = " + usedMemory + "MB"); + assertTrue(usedMemory < expectedMax, String.format( + "Used memory %d should be less than %d (Recommend running test manually to verify)", + usedMemory, + expectedMax)); + } + + /** + * Loads an alignment from file and exercises various operations in Jalview + * + * @param f + */ + protected void doStuffInJalview(File f) + { + /* + * load alignment, wait for consensus and other threads to complete + */ + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(), + DataSourceType.FILE); + while (af.getViewport().isCalcInProgress()) + { + waitFor(200); + } + + /* + * set a selection group - potential memory leak if it retains + * a reference to the alignment + */ + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(0); + sg.setEndRes(100); + AlignmentI al = af.viewport.getAlignment(); + for (int i = 0; i < al.getHeight(); i++) + { + sg.addSequence(al.getSequenceAt(i), false); + } + af.viewport.setSelectionGroup(sg); + + /* + * compute Tree and PCA (on all sequences, 100 columns) + */ + af.openTreePcaDialog(); + CalculationChooser dialog = af.alignPanel.getCalculationDialog(); + dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true)); + dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false)); + + /* + * wait until Tree and PCA have been computed + */ + while (af.viewport.getCurrentTree() == null + && dialog.getPcaPanel().isWorking()) + { + waitFor(10); + } + + /* + * give Swing time to add the PCA panel (?!?) + */ + waitFor(100); + } + + /** + * Wait for waitMs miliseconds + * + * @param waitMs + */ + protected void waitFor(int waitMs) + { + try + { + Thread.sleep(waitMs); + } catch (InterruptedException e) + { + } + } + + /** + * Generates an alignment and saves it in a temporary file, to be loaded by + * Jalview. We use a peptide alignment (so Conservation and Quality are + * calculated), which is wide enough to ensure Consensus, Conservation and + * Occupancy have a significant memory footprint (if not removed from the + * heap). + * + * @return + * @throws IOException + */ + private File generateAlignment() throws IOException + { + File f = File.createTempFile("MemoryTest", "fa"); + PrintStream ps = new PrintStream(f); + AlignmentGenerator ag = new AlignmentGenerator(false, ps); + int width = 100000; + int height = 100; + ag.generate(width, height, 0, 10, 15); + return f; + } +} diff --git a/test/jalview/gui/PairwiseAlignmentPanelTest.java b/test/jalview/gui/PairwiseAlignmentPanelTest.java new file mode 100644 index 0000000..3322ee8 --- /dev/null +++ b/test/jalview/gui/PairwiseAlignmentPanelTest.java @@ -0,0 +1,73 @@ +package jalview.gui; + +import static org.testng.Assert.assertEquals; + +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceGroup; +import jalview.io.DataSourceType; +import jalview.io.FileLoader; + +import javax.swing.JTextArea; + +import junit.extensions.PA; + +import org.testng.annotations.Test; + +public class PairwiseAlignmentPanelTest +{ + @Test(groups = "Functional") + public void testConstructor_withSelectionGroup() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewport viewport = af.getViewport(); + AlignmentI al = viewport.getAlignment(); + + /* + * select columns 29-36 of sequences 4 and 5 for alignment + * Q93XJ9_SOLTU/23-29 L-KAISNV + * FER1_PEA/26-32 V-TTTKAF + */ + SequenceGroup sg = new SequenceGroup(); + sg.addSequence(al.getSequenceAt(3), false); + sg.addSequence(al.getSequenceAt(4), false); + sg.setStartRes(28); + sg.setEndRes(35); + viewport.setSelectionGroup(sg); + + PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport); + + String text = ((JTextArea) PA.getValue(testee, "textarea")).getText(); + String expected = "Score = 80.0\n" + "Length of alignment = 4\n" + + "Sequence FER1_PEA/29-32 (Sequence length = 7)\n" + + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n" + + " FER1_PEA/29-32 TKAF\n" + " ||.\n" + + "Q93XJ9_SOLTU/23-26 LKAI\n\n" + "Percentage ID = 50.00\n\n"; + assertEquals(text, expected); + } + + /** + * This test aligns the same sequences as testConstructor_withSelectionGroup + * but as a complete alignment (no selection). Note that in fact the user is + * currently required to make a selection in order to calculate pairwise + * alignments, so this case does not arise. + */ + @Test(groups = "Functional") + public void testConstructor_noSelectionGroup() + { + String seqs = ">Q93XJ9_SOLTU/23-29\nL-KAISNV\n>FER1_PEA/26-32\nV-TTTKAF\n"; + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqs, + DataSourceType.PASTE); + AlignViewport viewport = af.getViewport(); + + PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport); + + String text = ((JTextArea) PA.getValue(testee, "textarea")).getText(); + String expected = "Score = 80.0\n" + "Length of alignment = 4\n" + + "Sequence FER1_PEA/29-32 (Sequence length = 7)\n" + + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n" + + " FER1_PEA/29-32 TKAF\n" + " ||.\n" + + "Q93XJ9_SOLTU/23-26 LKAI\n\n" + "Percentage ID = 50.00\n\n"; + assertEquals(text, expected); + } +} diff --git a/test/jalview/gui/ProgressBarTest.java b/test/jalview/gui/ProgressBarTest.java index a1715e9..72a288b 100644 --- a/test/jalview/gui/ProgressBarTest.java +++ b/test/jalview/gui/ProgressBarTest.java @@ -29,6 +29,7 @@ import java.awt.GridLayout; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -119,8 +120,15 @@ public class ProgressBarTest * @param layout * @param msgs */ - private void verifyProgress(GridLayout layout, String[] msgs) + private void verifyProgress(final GridLayout layout, final String[] msgs) { + try + { + SwingUtilities.invokeAndWait(new Runnable() + { + @Override + public void run() + { int msgCount = msgs.length; assertEquals(1 + msgCount, layout.getRows()); assertEquals(msgCount, statusPanel.getComponentCount()); @@ -132,5 +140,13 @@ public class ProgressBarTest assertEquals(msgs[i++], ((JLabel) ((JPanel) c).getComponent(0)).getText()); } + } + }); + } catch (Exception e) + { + throw new AssertionError( + "Unexpected exception waiting for progress bar validation", + e); + } } } diff --git a/test/jalview/gui/SeqCanvasTest.java b/test/jalview/gui/SeqCanvasTest.java new file mode 100644 index 0000000..a27bc3f --- /dev/null +++ b/test/jalview/gui/SeqCanvasTest.java @@ -0,0 +1,283 @@ +package jalview.gui; + +import static org.testng.Assert.assertEquals; + +import jalview.datamodel.AlignmentI; +import jalview.io.DataSourceType; +import jalview.io.FileLoader; + +import java.awt.Font; +import java.awt.FontMetrics; + +import junit.extensions.PA; + +import org.testng.annotations.Test; + +import sun.swing.SwingUtilities2; + +public class SeqCanvasTest +{ + /** + * Test the method that computes wrapped width in residues, height of wrapped + * widths in pixels, and the number of widths visible + */ + @Test(groups = "Functional") + public void testCalculateWrappedGeometry_noAnnotations() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewport av = af.getViewport(); + AlignmentI al = av.getAlignment(); + assertEquals(al.getWidth(), 157); + assertEquals(al.getHeight(), 15); + + av.setWrapAlignment(true); + av.getRanges().setStartEndSeq(0, 14); + av.setFont(new Font("SansSerif", Font.PLAIN, 14), true); + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + assertEquals(charHeight, 17); + assertEquals(charWidth, 12); + + SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas; + + /* + * first with scales above, left, right + */ + av.setShowAnnotation(false); + av.setScaleAboveWrapped(true); + av.setScaleLeftWrapped(true); + av.setScaleRightWrapped(true); + FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont()); + int labelWidth = fm.stringWidth("000") + charWidth; + assertEquals(labelWidth, 39); // 3 x 9 + charWidth + + /* + * width 400 pixels leaves (400 - 2*labelWidth) for residue columns + * take the whole multiple of character widths + */ + int canvasWidth = 400; + int canvasHeight = 300; + int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth; + int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(wrappedWidth, residueColumns); + assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth); + assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth); + assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"), + 2 * charHeight); + int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx"); + assertEquals(repeatingHeight, charHeight * (2 + al.getHeight())); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1); + + /* + * repeat height is 17 * (2 + 15) = 289 + * make canvas height 2 * 289 + 3 * charHeight so just enough to + * draw 2 widths and the first sequence of a third + */ + canvasHeight = charHeight * (17 * 2 + 3); + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce canvas height by 1 pixel - should not be enough height + * to draw 3 widths + */ + canvasHeight -= 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * turn off scale above - can now fit in 2 and a bit widths + */ + av.setScaleAboveWrapped(false); + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce height to enough for 2 widths and not quite a third + * i.e. two repeating heights + spacer + sequence - 1 pixel + */ + canvasHeight = charHeight * (16 * 2 + 2) - 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * make canvas width enough for scales and 20 residues + */ + canvasWidth = 2 * labelWidth + 20 * charWidth; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 20); + + /* + * reduce width by 1 pixel - rounds down to 19 residues + */ + canvasWidth -= 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 19); + + /* + * turn off West scale - adds labelWidth (39) to available for residues + * which with the 11 remainder makes 50 which is 4 more charWidths rem 2 + */ + av.setScaleLeftWrapped(false); + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 23); + + /* + * add 10 pixels to width to fit in another whole residue column + */ + canvasWidth += 9; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 23); + canvasWidth += 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 24); + + /* + * turn off East scale to gain 39 more pixels (3 columns remainder 3) + */ + av.setScaleRightWrapped(false); + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 27); + + /* + * add 9 pixels to width to gain a residue column + */ + canvasWidth += 8; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 27); + canvasWidth += 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 28); + + /* + * now West but not East scale - lose 39 pixels or 4 columns + */ + av.setScaleLeftWrapped(true); + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 24); + + /* + * adding 3 pixels to width regains one column + */ + canvasWidth += 2; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 24); + canvasWidth += 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 25); + + /* + * turn off scales left and right, make width exactly 157 columns + */ + av.setScaleLeftWrapped(false); + canvasWidth = al.getWidth() * charWidth; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1); + } + + /** + * Test the method that computes wrapped width in residues, height of wrapped + * widths in pixels, and the number of widths visible + */ + @Test(groups = "Functional") + public void testCalculateWrappedGeometry_withAnnotations() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewport av = af.getViewport(); + AlignmentI al = av.getAlignment(); + assertEquals(al.getWidth(), 157); + assertEquals(al.getHeight(), 15); + + av.setWrapAlignment(true); + av.getRanges().setStartEndSeq(0, 14); + av.setFont(new Font("SansSerif", Font.PLAIN, 14), true); + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + assertEquals(charHeight, 17); + assertEquals(charWidth, 12); + + SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas; + + /* + * first with scales above, left, right + */ + av.setShowAnnotation(true); + av.setScaleAboveWrapped(true); + av.setScaleLeftWrapped(true); + av.setScaleRightWrapped(true); + FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont()); + int labelWidth = fm.stringWidth("000") + charWidth; + assertEquals(labelWidth, 39); // 3 x 9 + charWidth + int annotationHeight = testee.getAnnotationHeight(); + + /* + * width 400 pixels leaves (400 - 2*labelWidth) for residue columns + * take the whole multiple of character widths + */ + int canvasWidth = 400; + int canvasHeight = 300; + int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth; + int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(wrappedWidth, residueColumns); + assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth); + assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth); + assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"), + 2 * charHeight); + int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx"); + assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()) + + annotationHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1); + + /* + * repeat height is 17 * (2 + 15) = 289 + annotationHeight = 507 + * make canvas height 2 * 289 + 3 * charHeight so just enough to + * draw 2 widths and the first sequence of a third + */ + canvasHeight = charHeight * (17 * 2 + 3) + 2 * annotationHeight; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce canvas height by 1 pixel - should not be enough height + * to draw 3 widths + */ + canvasHeight -= 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * turn off scale above - can now fit in 2 and a bit widths + */ + av.setScaleAboveWrapped(false); + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce height to enough for 2 widths and not quite a third + * i.e. two repeating heights + spacer + sequence - 1 pixel + */ + canvasHeight = charHeight * (16 * 2 + 2) + 2 * annotationHeight - 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * add 1 pixel to height - should now get 3 widths drawn + */ + canvasHeight += 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + } +} diff --git a/test/jalview/renderer/ScaleRendererTest.java b/test/jalview/renderer/ScaleRendererTest.java index cf1039f..0af67cd 100644 --- a/test/jalview/renderer/ScaleRendererTest.java +++ b/test/jalview/renderer/ScaleRendererTest.java @@ -26,11 +26,11 @@ public class ScaleRendererTest AlignViewport av = af.getViewport(); /* - * scale has minor ticks at 5 and 15, major at 10 and 20 + * scale has minor ticks at 5, 15, 25, major at 10 and 20 * (these are base 1, ScaleMark holds base 0 values) */ List marks = new ScaleRenderer().calculateMarks(av, 0, 25); - assertEquals(marks.size(), 4); + assertEquals(marks.size(), 5); assertFalse(marks.get(0).major); assertEquals(marks.get(0).column, 4); @@ -48,6 +48,10 @@ public class ScaleRendererTest assertEquals(marks.get(3).column, 19); assertEquals(marks.get(3).text, "20"); + assertFalse(marks.get(4).major); + assertEquals(marks.get(4).column, 24); + assertNull(marks.get(4).text); + /* * now hide columns 9-11 and 18-20 (base 1) * scale marks are now in the same columns as before, but @@ -56,7 +60,7 @@ public class ScaleRendererTest av.hideColumns(8, 10); av.hideColumns(17, 19); marks = new ScaleRenderer().calculateMarks(av, 0, 25); - assertEquals(marks.size(), 4); + assertEquals(marks.size(), 5); assertFalse(marks.get(0).major); assertEquals(marks.get(0).column, 4); assertNull(marks.get(0).text); @@ -69,5 +73,8 @@ public class ScaleRendererTest assertTrue(marks.get(3).major); assertEquals(marks.get(3).column, 19); assertEquals(marks.get(3).text, "26"); // +6 hidden columns + assertFalse(marks.get(4).major); + assertEquals(marks.get(4).column, 24); + assertNull(marks.get(4).text); } } diff --git a/test/jalview/structures/models/AAStructureBindingModelTest.java b/test/jalview/structures/models/AAStructureBindingModelTest.java index aea3687..af02d5e 100644 --- a/test/jalview/structures/models/AAStructureBindingModelTest.java +++ b/test/jalview/structures/models/AAStructureBindingModelTest.java @@ -275,11 +275,11 @@ public class AAStructureBindingModelTest StructureSelectionManager ssm = new StructureSelectionManager(); ssm.setMapping(new SequenceI[] { seq1a, seq1b }, null, PDB_1, - DataSourceType.PASTE); + DataSourceType.PASTE, null); ssm.setMapping(new SequenceI[] { seq2 }, null, PDB_2, - DataSourceType.PASTE); + DataSourceType.PASTE, null); ssm.setMapping(new SequenceI[] { seq3 }, null, PDB_3, - DataSourceType.PASTE); + DataSourceType.PASTE, null); testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, null) { diff --git a/test/jalview/util/MappingUtilsTest.java b/test/jalview/util/MappingUtilsTest.java index d0ec3e8..5226819 100644 --- a/test/jalview/util/MappingUtilsTest.java +++ b/test/jalview/util/MappingUtilsTest.java @@ -1149,4 +1149,49 @@ public class MappingUtilsTest assertEquals("[12, 11, 8, 4]", Arrays.toString(ranges)); } + @Test(groups = "Functional") + public void testRemoveEndPositions() + { + List ranges = new ArrayList<>(); + + /* + * case 1: truncate last range + */ + ranges.add(new int[] { 1, 10 }); + ranges.add(new int[] { 20, 30 }); + MappingUtils.removeEndPositions(5, ranges); + assertEquals(2, ranges.size()); + assertEquals(25, ranges.get(1)[1]); + + /* + * case 2: remove last range + */ + ranges.clear(); + ranges.add(new int[] { 1, 10 }); + ranges.add(new int[] { 20, 22 }); + MappingUtils.removeEndPositions(3, ranges); + assertEquals(1, ranges.size()); + assertEquals(10, ranges.get(0)[1]); + + /* + * case 3: truncate penultimate range + */ + ranges.clear(); + ranges.add(new int[] { 1, 10 }); + ranges.add(new int[] { 20, 21 }); + MappingUtils.removeEndPositions(3, ranges); + assertEquals(1, ranges.size()); + assertEquals(9, ranges.get(0)[1]); + + /* + * case 4: remove last two ranges + */ + ranges.clear(); + ranges.add(new int[] { 1, 10 }); + ranges.add(new int[] { 20, 20 }); + ranges.add(new int[] { 30, 30 }); + MappingUtils.removeEndPositions(3, ranges); + assertEquals(1, ranges.size()); + assertEquals(9, ranges.get(0)[1]); + } } diff --git a/test/jalview/viewmodel/ViewportRangesTest.java b/test/jalview/viewmodel/ViewportRangesTest.java index 851b1b7..c0cb4ba 100644 --- a/test/jalview/viewmodel/ViewportRangesTest.java +++ b/test/jalview/viewmodel/ViewportRangesTest.java @@ -763,6 +763,66 @@ public class ViewportRangesTest { } } } + + @Test(groups = { "Functional" }) + public void testScrollUp_wrapped() + { + /* + * alignment 30 tall and 45 wide + */ + AlignmentI al2 = gen.generate(45, 30, 1, 0, 5); + + /* + * wrapped view, 5 sequences high, start at sequence offset 1 + */ + ViewportRanges vr = new ViewportRanges(al2); + vr.setWrappedMode(true); + vr.setViewportStartAndHeight(1, 5); + + /* + * offset wrapped view to column 3 + */ + vr.setStartEndRes(3, 22); + + int startRes = vr.getStartRes(); + int width = vr.getViewportWidth(); + assertEquals(startRes, 3); + assertEquals(width, 20); + + // in wrapped mode, we change startRes but not startSeq + // scroll down: + vr.scrollUp(false); + assertEquals(vr.getStartSeq(), 1); + assertEquals(vr.getStartRes(), 23); + + // scroll up returns to original position + vr.scrollUp(true); + assertEquals(vr.getStartSeq(), 1); + assertEquals(vr.getStartRes(), 3); + + // scroll up again returns to 'origin' + vr.scrollUp(true); + assertEquals(vr.getStartSeq(), 1); + assertEquals(vr.getStartRes(), 0); + + /* + * offset 3 columns once more and do some scroll downs + */ + vr.setStartEndRes(3, 22); + vr.scrollUp(false); + assertEquals(vr.getStartSeq(), 1); + assertEquals(vr.getStartRes(), 23); + vr.scrollUp(false); + assertEquals(vr.getStartSeq(), 1); + assertEquals(vr.getStartRes(), 43); + + /* + * scroll down beyond end of alignment does nothing + */ + vr.scrollUp(false); + assertEquals(vr.getStartSeq(), 1); + assertEquals(vr.getStartRes(), 43); + } } // mock listener for property change events