/* * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8) * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * * Jalview is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with Jalview. If not, see . */ package jalview.viewmodel; import jalview.analysis.Conservation; import jalview.api.AlignCalcManagerI; import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentView; import jalview.datamodel.Annotation; import jalview.datamodel.ColumnSelection; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.schemes.ClustalxColourScheme; import jalview.schemes.ColourSchemeI; import jalview.schemes.ResidueProperties; import jalview.workers.AlignCalcManager; import jalview.workers.ConsensusThread; import jalview.workers.ConservationThread; import jalview.workers.StrucConsensusThread; import java.util.Hashtable; import java.util.Vector; /** * base class holding visualization and analysis attributes and common logic for an active alignment view displayed in the GUI * @author jimp * */ public abstract class AlignmentViewport implements AlignViewportI { /** * alignment displayed in the viewport. Please use get/setter */ protected AlignmentI alignment; protected String sequenceSetID; /** * probably unused indicator that view is of a dataset rather than an alignment */ protected boolean isDataset = false; private Hashtable hiddenRepSequences; protected ColumnSelection colSel = new ColumnSelection(); public boolean autoCalculateConsensus = true; protected boolean autoCalculateStrucConsensus = true; protected boolean ignoreGapsInConsensusCalculation = false; protected ColourSchemeI globalColourScheme = null; public void setGlobalColourScheme(ColourSchemeI cs) { globalColourScheme = cs; } public ColourSchemeI getGlobalColourScheme() { return globalColourScheme; } protected AlignmentAnnotation consensus; protected AlignmentAnnotation strucConsensus; protected AlignmentAnnotation conservation; protected AlignmentAnnotation quality; protected AlignmentAnnotation[] groupConsensus; protected AlignmentAnnotation[] groupConservation; /** * results of alignment consensus analysis for visible portion of view */ protected Hashtable[] hconsensus=null; /** * results of secondary structure base pair consensus for visible portion of view */ protected Hashtable[] hStrucConsensus=null; /** * percentage gaps allowed in a column before all amino acid properties should be considered unconserved */ int ConsPercGaps = 25; // JBPNote : This should be a scalable property! public int getConsPercGaps() { return ConsPercGaps; } @Override public void setSequenceConsensusHash(Hashtable[] hconsensus) { this.hconsensus=hconsensus; } @Override public Hashtable[] getSequenceConsensusHash() { return hconsensus; } @Override public Hashtable[] getRnaStructureConsensusHash() { return hStrucConsensus; } @Override public void setRnaStructureConsensusHash(Hashtable[] hStrucConsensus) { this.hStrucConsensus=hStrucConsensus; } @Override public AlignmentAnnotation getAlignmentQualityAnnot() { return quality; } @Override public AlignmentAnnotation getAlignmentConservationAnnotation() { return conservation; } @Override public AlignmentAnnotation getAlignmentConsensusAnnotation() { return consensus; } @Override public AlignmentAnnotation getAlignmentStrucConsensusAnnotation() { return strucConsensus; } protected AlignCalcManagerI calculator=new AlignCalcManager(); jalview.workers.ConsensusThread consensusThread; StrucConsensusThread strucConsensusThread; private ConservationThread conservationThread; /** * trigger update of conservation annotation */ public void updateConservation(final AlignmentViewPanel ap) { // see note in mantis : issue number 8585 if (alignment.isNucleotide() || conservation == null || !autoCalculateConsensus) { return; } calculator.startWorker(conservationThread=new jalview.workers.ConservationThread(this, ap)); } /** * trigger update of consensus annotation */ public void updateConsensus(final AlignmentViewPanel ap) { // see note in mantis : issue number 8585 if (consensus == null || !autoCalculateConsensus) { return; } calculator.startWorker(consensusThread = new ConsensusThread(this, ap)); } // --------START Structure Conservation public void updateStrucConsensus(final AlignmentViewPanel ap) { if (autoCalculateStrucConsensus && strucConsensus==null && alignment.isNucleotide() && alignment.hasRNAStructure()) { } // see note in mantis : issue number 8585 if (strucConsensus == null || !autoCalculateStrucConsensus) { return; } calculator.startWorker(strucConsensusThread = new StrucConsensusThread(this,ap)); } public boolean isCalcInProgress() { return calculator.isWorking(); } public boolean isCalculationInProgress( AlignmentAnnotation alignmentAnnotation) { if (!alignmentAnnotation.autoCalculated) return false; if ((calculator.isWorking(consensusThread) && consensus==alignmentAnnotation) || (calculator.isWorking(conservationThread) && (conservation==alignmentAnnotation || quality==alignmentAnnotation)) || (calculator.isWorking(strucConsensusThread) && strucConsensus==alignmentAnnotation) ) { return true; } return false; } @Override public boolean isClosed() { // TODO: check that this isClosed is only true after panel is closed, not before it is fully constructed. return alignment==null; } @Override public AlignCalcManagerI getCalcManager() { return calculator; } /** * should conservation rows be shown for groups */ protected boolean showGroupConservation = false; /** * should consensus rows be shown for groups */ protected boolean showGroupConsensus = false; /** * should consensus profile be rendered by default */ protected boolean showSequenceLogo = false; /** * should consensus profile be rendered normalised to row height */ protected boolean normaliseSequenceLogo = false; /** * should consensus histograms be rendered by default */ protected boolean showConsensusHistogram = true; /** * @return the showConsensusProfile */ public boolean isShowSequenceLogo() { return showSequenceLogo; } /** * @param showSequenceLogo * the new value */ public void setShowSequenceLogo(boolean showSequenceLogo) { if (showSequenceLogo != this.showSequenceLogo) { // TODO: decouple settings setting from calculation when refactoring // annotation update method from alignframe to viewport this.showSequenceLogo = showSequenceLogo; if (consensusThread != null) { consensusThread.updateAnnotation(); } if (strucConsensusThread != null) { strucConsensusThread.updateAnnotation(); } } this.showSequenceLogo = showSequenceLogo; } /** * @param showConsensusHistogram * the showConsensusHistogram to set */ public void setShowConsensusHistogram(boolean showConsensusHistogram) { this.showConsensusHistogram = showConsensusHistogram; } /** * @return the showGroupConservation */ public boolean isShowGroupConservation() { return showGroupConservation; } /** * @param showGroupConservation * the showGroupConservation to set */ public void setShowGroupConservation(boolean showGroupConservation) { this.showGroupConservation = showGroupConservation; } /** * @return the showGroupConsensus */ public boolean isShowGroupConsensus() { return showGroupConsensus; } /** * @param showGroupConsensus * the showGroupConsensus to set */ public void setShowGroupConsensus(boolean showGroupConsensus) { this.showGroupConsensus = showGroupConsensus; } /** * * @return flag to indicate if the consensus histogram should be rendered by * default */ public boolean isShowConsensusHistogram() { return this.showConsensusHistogram; } /** * show non-conserved residues only */ protected boolean showUnconserved = false; /** * when set, updateAlignment will always ensure sequences are of equal length */ private boolean padGaps = false; /** * when set, alignment should be reordered according to a newly opened tree */ public boolean sortByTree = false; public boolean getShowUnconserved() { return showUnconserved; } public void setShowUnconserved(boolean showunconserved) { showUnconserved = showunconserved; } /** * @param showNonconserved * the showUnconserved to set */ public void setShowunconserved(boolean displayNonconserved) { this.showUnconserved = displayNonconserved; } /** * * * @return null or the currently selected sequence region */ public SequenceGroup getSelectionGroup() { return selectionGroup; } /** * Set the selection group for this window. * * @param sg * - group holding references to sequences in this alignment view * */ public void setSelectionGroup(SequenceGroup sg) { selectionGroup = sg; } public void setHiddenColumns(ColumnSelection colsel) { this.colSel = colsel; if (colSel.getHiddenColumns() != null) { hasHiddenColumns = true; } } public ColumnSelection getColumnSelection() { return colSel; } public void setColumnSelection(ColumnSelection colSel) { this.colSel=colSel; } public Hashtable getHiddenRepSequences() { return hiddenRepSequences; } public void setHiddenRepSequences(Hashtable hiddenRepSequences) { this.hiddenRepSequences = hiddenRepSequences; } protected boolean hasHiddenColumns = false; public void updateHiddenColumns() { hasHiddenColumns = colSel.getHiddenColumns() != null; } protected boolean hasHiddenRows = false; public boolean hasHiddenRows() { return hasHiddenRows; } protected SequenceGroup selectionGroup; public void setSequenceSetId(String newid) { if (sequenceSetID!=null) { System.err.println("Warning - overwriting a sequenceSetId for a viewport!"); } sequenceSetID=new String(newid); } public String getSequenceSetId() { if (sequenceSetID == null) { sequenceSetID = alignment.hashCode() + ""; } return sequenceSetID; } /** * unique viewId for synchronizing state (e.g. with stored Jalview Project) * */ protected String viewId = null; public String getViewId() { if (viewId == null) { viewId = this.getSequenceSetId() + "." + this.hashCode() + ""; } return viewId; } public void setIgnoreGapsConsensus(boolean b, AlignmentViewPanel ap) { ignoreGapsInConsensusCalculation = b; if (ap!=null) {updateConsensus(ap); if (globalColourScheme != null) { globalColourScheme.setThreshold(globalColourScheme.getThreshold(), ignoreGapsInConsensusCalculation); }} } private long sgrouphash = -1, colselhash = -1; /** * checks current SelectionGroup against record of last hash value, and * updates record. * * @param b * update the record of last hash value * * @return true if SelectionGroup changed since last call (when b is true) */ public boolean isSelectionGroupChanged(boolean b) { int hc = (selectionGroup == null || selectionGroup.getSize() == 0) ? -1 : selectionGroup.hashCode(); if (hc != -1 && hc != sgrouphash) { if (b) { sgrouphash = hc; } return true; } return false; } /** * checks current colsel against record of last hash value, and optionally * updates record. * * @param b * update the record of last hash value * @return true if colsel changed since last call (when b is true) */ public boolean isColSelChanged(boolean b) { int hc = (colSel == null || colSel.size() == 0) ? -1 : colSel .hashCode(); if (hc != -1 && hc != colselhash) { if (b) { colselhash = hc; } return true; } return false; } public boolean getIgnoreGapsConsensus() { return ignoreGapsInConsensusCalculation; } /// property change stuff // JBPNote Prolly only need this in the applet version. private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport( this); protected boolean showConservation = true; protected boolean showQuality = true; protected boolean showConsensus = true; /** * Property change listener for changes in alignment * * @param listener * DOCUMENT ME! */ public void addPropertyChangeListener( java.beans.PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } /** * DOCUMENT ME! * * @param listener * DOCUMENT ME! */ public void removePropertyChangeListener( java.beans.PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } /** * Property change listener for changes in alignment * * @param prop * DOCUMENT ME! * @param oldvalue * DOCUMENT ME! * @param newvalue * DOCUMENT ME! */ public void firePropertyChange(String prop, Object oldvalue, Object newvalue) { changeSupport.firePropertyChange(prop, oldvalue, newvalue); } // common hide/show column stuff public void hideSelectedColumns() { if (colSel.size() < 1) { return; } colSel.hideSelectedColumns(); setSelectionGroup(null); hasHiddenColumns = true; } public void hideColumns(int start, int end) { if (start == end) { colSel.hideColumns(start); } else { colSel.hideColumns(start, end); } hasHiddenColumns = true; } public void showColumn(int col) { colSel.revealHiddenColumns(col); if (colSel.getHiddenColumns() == null) { hasHiddenColumns = false; } } public void showAllHiddenColumns() { colSel.revealAllHiddenColumns(); hasHiddenColumns = false; } // common hide/show seq stuff public void showAllHiddenSeqs() { if (alignment.getHiddenSequences().getSize() > 0) { if (selectionGroup == null) { selectionGroup = new SequenceGroup(); selectionGroup.setEndRes(alignment.getWidth() - 1); } Vector tmp = alignment.getHiddenSequences().showAll( hiddenRepSequences); for (int t = 0; t < tmp.size(); t++) { selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false); } hasHiddenRows = false; hiddenRepSequences = null; firePropertyChange("alignment", null, alignment.getSequences()); // used to set hasHiddenRows/hiddenRepSequences here, after the property changed event sendSelection(); } } public void showSequence(int index) { Vector tmp = alignment.getHiddenSequences().showSequence(index, hiddenRepSequences); if (tmp.size() > 0) { if (selectionGroup == null) { selectionGroup = new SequenceGroup(); selectionGroup.setEndRes(alignment.getWidth() - 1); } for (int t = 0; t < tmp.size(); t++) { selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false); } // JBPNote: refactor: only update flag if we modified visiblity (used to do this regardless) if (alignment.getHiddenSequences().getSize() < 1) { hasHiddenRows = false; } firePropertyChange("alignment", null, alignment.getSequences()); sendSelection(); } } public void hideAllSelectedSeqs() { if (selectionGroup == null || selectionGroup.getSize() < 1) { return; } SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment); hideSequence(seqs); setSelectionGroup(null); } public void hideSequence(SequenceI[] seq) { if (seq != null) { for (int i = 0; i < seq.length; i++) { alignment.getHiddenSequences().hideSequence(seq[i]); } hasHiddenRows = true; firePropertyChange("alignment", null, alignment.getSequences()); } } public void hideRepSequences(SequenceI repSequence, SequenceGroup sg) { int sSize = sg.getSize(); if (sSize < 2) { return; } if (hiddenRepSequences == null) { hiddenRepSequences = new Hashtable(); } hiddenRepSequences.put(repSequence, sg); // Hide all sequences except the repSequence SequenceI[] seqs = new SequenceI[sSize - 1]; int index = 0; for (int i = 0; i < sSize; i++) { if (sg.getSequenceAt(i) != repSequence) { if (index == sSize - 1) { return; } seqs[index++] = sg.getSequenceAt(i); } } sg.setSeqrep(repSequence); // note: not done in 2.7applet sg.setHidereps(true); // note: not done in 2.7applet hideSequence(seqs); } public boolean isHiddenRepSequence(SequenceI seq) { return hiddenRepSequences != null && hiddenRepSequences.containsKey(seq); } public SequenceGroup getRepresentedSequences(SequenceI seq) { return (SequenceGroup) (hiddenRepSequences == null ? null : hiddenRepSequences.get(seq)); } public int adjustForHiddenSeqs(int alignmentIndex) { return alignment.getHiddenSequences().adjustForHiddenSeqs( alignmentIndex); } // Selection manipulation /** * broadcast selection to any interested parties */ public abstract void sendSelection(); public void invertColumnSelection() { colSel.invertColumnSelection(0, alignment.getWidth()); } /** * This method returns an array of new SequenceI objects derived from the * whole alignment or just the current selection with start and end points * adjusted * * @note if you need references to the actual SequenceI objects in the * alignment or currently selected then use getSequenceSelection() * @return selection as new sequenceI objects */ public SequenceI[] getSelectionAsNewSequence() { SequenceI[] sequences; // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom - this was the only caller in the applet for this method // JBPNote: in applet, this method returned references to the alignment sequences, and it did not honour the presence/absence of annotation attached to the alignment (probably!) if (selectionGroup == null) { sequences = alignment.getSequencesArray(); AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation(); for (int i = 0; i < sequences.length; i++) { sequences[i] = new Sequence(sequences[i], annots); // construct new // sequence with // subset of visible // annotation } } else { sequences = selectionGroup.getSelectionAsNewSequences(alignment); } return sequences; } /** * get the currently selected sequence objects or all the sequences in the * alignment. * * @return array of references to sequence objects */ public SequenceI[] getSequenceSelection() { SequenceI[] sequences = null; if (selectionGroup != null) { sequences = selectionGroup.getSequencesInOrder(alignment); } if (sequences == null) { sequences = alignment.getSequencesArray(); } return sequences; } /** * This method returns the visible alignment as text, as seen on the GUI, ie * if columns are hidden they will not be returned in the result. Use this for * calculating trees, PCA, redundancy etc on views which contain hidden * columns. * * @return String[] */ public jalview.datamodel.CigarArray getViewAsCigars( boolean selectedRegionOnly) { return new jalview.datamodel.CigarArray(alignment, (hasHiddenColumns ? colSel : null), (selectedRegionOnly ? selectionGroup : null)); } /** * return a compact representation of the current alignment selection to pass * to an analysis function * * @param selectedOnly * boolean true to just return the selected view * @return AlignmentView */ public jalview.datamodel.AlignmentView getAlignmentView( boolean selectedOnly) { return getAlignmentView(selectedOnly, false); } /** * return a compact representation of the current alignment selection to pass * to an analysis function * * @param selectedOnly * boolean true to just return the selected view * @param markGroups * boolean true to annotate the alignment view with groups on the * alignment (and intersecting with selected region if selectedOnly * is true) * @return AlignmentView */ public jalview.datamodel.AlignmentView getAlignmentView( boolean selectedOnly, boolean markGroups) { return new AlignmentView(alignment, colSel, selectionGroup, hasHiddenColumns, selectedOnly, markGroups); } /** * This method returns the visible alignment as text, as seen on the GUI, ie * if columns are hidden they will not be returned in the result. Use this for * calculating trees, PCA, redundancy etc on views which contain hidden * columns. * * @return String[] */ public String[] getViewAsString(boolean selectedRegionOnly) { String[] selection = null; SequenceI[] seqs = null; int i, iSize; int start = 0, end = 0; if (selectedRegionOnly && selectionGroup != null) { iSize = selectionGroup.getSize(); seqs = selectionGroup.getSequencesInOrder(alignment); start = selectionGroup.getStartRes(); end = selectionGroup.getEndRes() + 1; } else { iSize = alignment.getHeight(); seqs = alignment.getSequencesArray(); end = alignment.getWidth(); } selection = new String[iSize]; if (hasHiddenColumns) { selection = colSel.getVisibleSequenceStrings(start, end, seqs); } else { for (i = 0; i < iSize; i++) { selection[i] = seqs[i].getSequenceAsString(start, end); } } return selection; } /** * return visible region boundaries within given column range * @param min first column (inclusive, from 0) * @param max last column (exclusive) * @return int[][] range of {start,end} visible positions */ public int[][] getVisibleRegionBoundaries(int min, int max) { Vector regions = new Vector(); int start = min; int end = max; do { if (hasHiddenColumns) { if (start == 0) { start = colSel.adjustForHiddenColumns(start); } end = colSel.getHiddenBoundaryRight(start); if (start == end) { end = max; } if (end > max) { end = max; } } regions.addElement(new int[] { start, end }); if (hasHiddenColumns) { start = colSel.adjustForHiddenColumns(end); start = colSel.getHiddenBoundaryLeft(start) + 1; } } while (end < max); int[][] startEnd = new int[regions.size()][2]; regions.copyInto(startEnd); return startEnd; } /** * @return the padGaps */ public boolean isPadGaps() { return padGaps; } /** * @param padGaps the padGaps to set */ public void setPadGaps(boolean padGaps) { this.padGaps = padGaps; } /** * apply any post-edit constraints and trigger any calculations needed after an edit has been performed on the alignment * @param ap */ public void alignmentChanged(AlignmentViewPanel ap) { if (isPadGaps()) { alignment.padGaps(); } if (autoCalculateConsensus) { updateConsensus(ap); } if (hconsensus != null && autoCalculateConsensus) { updateConservation(ap); } if (autoCalculateStrucConsensus) { updateStrucConsensus(ap); } // Reset endRes of groups if beyond alignment width int alWidth = alignment.getWidth(); Vector groups = alignment.getGroups(); if (groups != null) { for (int i = 0; i < groups.size(); i++) { SequenceGroup sg = (SequenceGroup) groups.elementAt(i); if (sg.getEndRes() > alWidth) { sg.setEndRes(alWidth - 1); } } } if (selectionGroup != null && selectionGroup.getEndRes() > alWidth) { selectionGroup.setEndRes(alWidth - 1); } resetAllColourSchemes(); // alignment.adjustSequenceAnnotations(); } /** * reset scope and do calculations for all applied colourschemes on alignment */ void resetAllColourSchemes() { ColourSchemeI cs = globalColourScheme; if (cs != null) { if (cs instanceof ClustalxColourScheme) { ((ClustalxColourScheme) cs).resetClustalX(alignment.getSequences(), alignment.getWidth()); } cs.setConsensus(hconsensus); if (cs.conservationApplied()) { cs.setConservation(Conservation.calculateConservation("All", ResidueProperties.propHash, 3, alignment.getSequences(), 0, alignment.getWidth(), false, getConsPercGaps(), false)); } } int s, sSize = alignment.getGroups().size(); for (s = 0; s < sSize; s++) { SequenceGroup sg = (SequenceGroup) alignment.getGroups().elementAt(s); if (sg.cs != null && sg.cs instanceof ClustalxColourScheme) { ((ClustalxColourScheme) sg.cs).resetClustalX(sg .getSequences(hiddenRepSequences), sg.getWidth()); } sg.recalcConservation(); } } protected void initAutoAnnotation() { // TODO: add menu option action that nulls or creates consensus object // depending on if the user wants to see the annotation or not in a // specific alignment if (hconsensus == null && !isDataset) { if (!alignment.isNucleotide()) { if (showConservation) { if (conservation==null) { conservation = new AlignmentAnnotation("Conservation", "Conservation of total alignment less than " + getConsPercGaps() + "% gaps", new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH); conservation.hasText = true; conservation.autoCalculated = true; alignment.addAnnotation(conservation); } } if (showQuality) { if (quality==null) { quality = new AlignmentAnnotation("Quality", "Alignment Quality based on Blosum62 scores", new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH); quality.hasText = true; quality.autoCalculated = true; alignment.addAnnotation(quality); } } } else { if (alignment.hasRNAStructure()) { strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID", new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH); strucConsensus.hasText = true; strucConsensus.autoCalculated = true; } } consensus = new AlignmentAnnotation("Consensus", "PID", new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH); consensus.hasText = true; consensus.autoCalculated = true; if (showConsensus) { alignment.addAnnotation(consensus); if (strucConsensus!=null) { alignment.addAnnotation(strucConsensus); } } } } }