From: gmungoc Date: Sun, 28 Dec 2014 09:54:56 +0000 (+0000) Subject: JAL-845 first working linked edit protein -> cDNA X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=2a4301b8fdda9f4e9d77473402d9b91b51009735;p=jalview.git JAL-845 first working linked edit protein -> cDNA --- diff --git a/src/MCview/AppletPDBCanvas.java b/src/MCview/AppletPDBCanvas.java index a1bb272..a196edd 100644 --- a/src/MCview/AppletPDBCanvas.java +++ b/src/MCview/AppletPDBCanvas.java @@ -20,19 +20,34 @@ */ package MCview; -import java.io.*; -import java.util.*; +import jalview.analysis.AlignSeq; +import jalview.appletgui.AlignmentPanel; +import jalview.appletgui.FeatureRenderer; +import jalview.appletgui.SequenceRenderer; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SequenceI; +import jalview.structure.AtomSpec; +import jalview.structure.StructureListener; +import jalview.structure.StructureMapping; +import jalview.structure.StructureSelectionManager; +import jalview.util.MessageManager; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Event; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Image; // JBPNote TODO: This class is quite noisy - needs proper log.info/log.debug -import java.awt.*; -import java.awt.event.*; - -import jalview.analysis.*; -import jalview.datamodel.*; - -import jalview.appletgui.*; -import jalview.structure.*; -import jalview.util.MessageManager; +import java.awt.Panel; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.io.PrintStream; +import java.util.List; +import java.util.Vector; public class AppletPDBCanvas extends Panel implements MouseListener, MouseMotionListener, StructureListener @@ -144,7 +159,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener, pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol); if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE)) + { pdbentry.setFile("INLINE" + pdb.id); + } } catch (Exception ex) { @@ -172,10 +189,10 @@ public class AppletPDBCanvas extends Panel implements MouseListener, { mappingDetails.append("\n\nPDB Sequence is :\nSequence = " - + ((PDBChain) pdb.chains.elementAt(i)).sequence + + pdb.chains.elementAt(i).sequence .getSequenceAsString()); mappingDetails.append("\nNo of residues = " - + ((PDBChain) pdb.chains.elementAt(i)).residues.size() + + pdb.chains.elementAt(i).residues.size() + "\n\n"); // Now lets compare the sequences to get @@ -183,8 +200,8 @@ public class AppletPDBCanvas extends Panel implements MouseListener, // Align the sequence to the pdb // TODO: DNa/Pep switch AlignSeq as = new AlignSeq(sequence, - ((PDBChain) pdb.chains.elementAt(i)).sequence, - ((PDBChain) pdb.chains.elementAt(i)).isNa ? AlignSeq.DNA + pdb.chains.elementAt(i).sequence, + pdb.chains.elementAt(i).isNa ? AlignSeq.DNA : AlignSeq.PEP); as.calcScoreMatrix(); as.traceAlignment(); @@ -218,7 +235,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener, mappingDetails.append("\nSEQ start/end " + seqstart + " " + seqend); } - mainchain = (PDBChain) pdb.chains.elementAt(maxchain); + mainchain = pdb.chains.elementAt(maxchain); mainchain.pdbstart = pdbstart; mainchain.pdbend = pdbend; @@ -277,9 +294,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - if (((PDBChain) pdb.chains.elementAt(ii)).isVisible) + if (pdb.chains.elementAt(ii).isVisible) { - Vector tmp = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector tmp = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < tmp.size(); i++) { @@ -308,9 +325,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - if (((PDBChain) pdb.chains.elementAt(ii)).isVisible) + if (pdb.chains.elementAt(ii).isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -379,9 +396,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener, } } - width[0] = (float) Math.abs(max[0] - min[0]); - width[1] = (float) Math.abs(max[1] - min[1]); - width[2] = (float) Math.abs(max[2] - min[2]); + width[0] = Math.abs(max[0] - min[0]); + width[1] = Math.abs(max[1] - min[1]); + width[2] = Math.abs(max[2] - min[2]); maxwidth = width[0]; @@ -438,9 +455,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener, // Find centre coordinate for (int ii = 0; ii < pdb.chains.size(); ii++) { - if (((PDBChain) pdb.chains.elementAt(ii)).isVisible) + if (pdb.chains.elementAt(ii).isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; bsize += bonds.size(); @@ -567,7 +584,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener, { for (int ii = 0; ii < pdb.chains.size(); ii++) { - chain = (PDBChain) pdb.chains.elementAt(ii); + chain = pdb.chains.elementAt(ii); for (int i = 0; i < chain.bonds.size(); i++) { @@ -779,7 +796,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener, repaint(); if (foundchain != -1) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(foundchain); + PDBChain chain = pdb.chains.elementAt(foundchain); if (chain == mainchain) { if (fatom.alignmentMapping != -1) @@ -825,7 +842,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener, PDBChain chain = null; if (foundchain != -1) { - chain = (PDBChain) pdb.chains.elementAt(foundchain); + chain = pdb.chains.elementAt(foundchain); if (chain == mainchain) { mouseOverStructure(fatom.resNumber, chain.id); @@ -875,18 +892,18 @@ public class AppletPDBCanvas extends Panel implements MouseListener, if ((evt.getModifiers() & Event.META_MASK) != 0) { - objmat.rotatez((float) ((mx - omx))); + objmat.rotatez(((mx - omx))); } else { - objmat.rotatex((float) ((omy - my))); - objmat.rotatey((float) ((omx - mx))); + objmat.rotatex(((omy - my))); + objmat.rotatey(((omx - mx))); } // Alter the bonds for (int ii = 0; ii < pdb.chains.size(); ii++) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -927,11 +944,11 @@ public class AppletPDBCanvas extends Panel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(ii); + PDBChain chain = pdb.chains.elementAt(ii); if (chain.isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -985,13 +1002,13 @@ public class AppletPDBCanvas extends Panel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(ii); + PDBChain chain = pdb.chains.elementAt(ii); int truex; Bond tmpBond = null; if (chain.isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -1032,7 +1049,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener, if (fatom != null) // )&& chain.ds != null) { - chain = (PDBChain) pdb.chains.elementAt(foundchain); + chain = pdb.chains.elementAt(foundchain); } } @@ -1100,7 +1117,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener, { for (int ii = 0; ii < pdb.chains.size(); ii++) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(ii); + PDBChain chain = pdb.chains.elementAt(ii); chain.isVisible = b; } mainchain.isVisible = true; @@ -1121,7 +1138,9 @@ public class AppletPDBCanvas extends Panel implements MouseListener, public void mouseOverStructure(int pdbResNum, String chain) { if (lastMessage == null || !lastMessage.equals(pdbResNum + chain)) + { ssm.mouseOverStructure(pdbResNum, chain, pdbentry.getFile()); + } lastMessage = pdbResNum + chain; } @@ -1130,19 +1149,40 @@ public class AppletPDBCanvas extends Panel implements MouseListener, StringBuffer eval = new StringBuffer(); - public void highlightAtom(int atomIndex, int pdbResNum, String chain, - String pdbfile) + /** + * Highlight the specified atoms in the structure. + * + * @param atoms + */ + @Override + public void highlightAtoms(List atoms) { if (!seqColoursReady) { return; } - - if (highlightRes != null && highlightRes.contains((atomIndex - 1) + "")) + for (AtomSpec atom : atoms) { - return; + int atomIndex = atom.getAtomIndex(); + + if (highlightRes != null + && highlightRes.contains((atomIndex - 1) + "")) + { + continue; + } + + highlightAtom(atomIndex); } + redrawneeded = true; + repaint(); + } + + /** + * @param atomIndex + */ + protected void highlightAtom(int atomIndex) + { int index = -1; Bond tmpBond; for (index = 0; index < mainchain.bonds.size(); index++) @@ -1178,9 +1218,6 @@ public class AppletPDBCanvas extends Panel implements MouseListener, break; } } - - redrawneeded = true; - repaint(); } public Color getColour(int atomIndex, int pdbResNum, String chain, diff --git a/src/MCview/PDBCanvas.java b/src/MCview/PDBCanvas.java index dc0f718..40bd370 100644 --- a/src/MCview/PDBCanvas.java +++ b/src/MCview/PDBCanvas.java @@ -20,18 +20,37 @@ */ package MCview; -import java.io.*; -import java.util.*; - +import jalview.analysis.AlignSeq; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SequenceI; +import jalview.gui.AlignmentPanel; +import jalview.gui.FeatureRenderer; +import jalview.gui.SequenceRenderer; +import jalview.structure.AtomSpec; +import jalview.structure.StructureListener; +import jalview.structure.StructureMapping; +import jalview.structure.StructureSelectionManager; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Event; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; // JBPNote TODO: This class is quite noisy - needs proper log.info/log.debug -import java.awt.*; -import java.awt.event.*; -import javax.swing.*; - -import jalview.analysis.*; -import jalview.datamodel.*; -import jalview.gui.*; -import jalview.structure.*; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.io.PrintStream; +import java.util.List; +import java.util.Vector; + +import javax.swing.JPanel; +import javax.swing.ToolTipManager; public class PDBCanvas extends JPanel implements MouseListener, MouseMotionListener, StructureListener @@ -134,7 +153,9 @@ public class PDBCanvas extends JPanel implements MouseListener, pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol); if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE)) + { pdbentry.setFile("INLINE" + pdb.id); + } } catch (Exception ex) { @@ -167,17 +188,17 @@ public class PDBCanvas extends JPanel implements MouseListener, { mappingDetails.append("\n\nPDB Sequence is :\nSequence = " - + ((PDBChain) pdb.chains.elementAt(i)).sequence + + pdb.chains.elementAt(i).sequence .getSequenceAsString()); mappingDetails.append("\nNo of residues = " - + ((PDBChain) pdb.chains.elementAt(i)).residues.size() + + pdb.chains.elementAt(i).residues.size() + "\n\n"); // Now lets compare the sequences to get // the start and end points. // Align the sequence to the pdb AlignSeq as = new AlignSeq(sequence, - ((PDBChain) pdb.chains.elementAt(i)).sequence, "pep"); + pdb.chains.elementAt(i).sequence, "pep"); as.calcScoreMatrix(); as.traceAlignment(); PrintStream ps = new PrintStream(System.out) @@ -210,7 +231,7 @@ public class PDBCanvas extends JPanel implements MouseListener, mappingDetails.append("\nSEQ start/end " + seqstart + " " + seqend); } - mainchain = (PDBChain) pdb.chains.elementAt(maxchain); + mainchain = pdb.chains.elementAt(maxchain); mainchain.pdbstart = pdbstart; mainchain.pdbend = pdbend; @@ -254,9 +275,9 @@ public class PDBCanvas extends JPanel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - if (((PDBChain) pdb.chains.elementAt(ii)).isVisible) + if (pdb.chains.elementAt(ii).isVisible) { - Vector tmp = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector tmp = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < tmp.size(); i++) { @@ -286,9 +307,9 @@ public class PDBCanvas extends JPanel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - if (((PDBChain) pdb.chains.elementAt(ii)).isVisible) + if (pdb.chains.elementAt(ii).isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -362,9 +383,9 @@ public class PDBCanvas extends JPanel implements MouseListener, * System.out.println("zmax " + max[2] + " min " + min[2]); */ - width[0] = (float) Math.abs(max[0] - min[0]); - width[1] = (float) Math.abs(max[1] - min[1]); - width[2] = (float) Math.abs(max[2] - min[2]); + width[0] = Math.abs(max[0] - min[0]); + width[1] = Math.abs(max[1] - min[1]); + width[2] = Math.abs(max[2] - min[2]); maxwidth = width[0]; @@ -421,9 +442,9 @@ public class PDBCanvas extends JPanel implements MouseListener, // Find centre coordinate for (int ii = 0; ii < pdb.chains.size(); ii++) { - if (((PDBChain) pdb.chains.elementAt(ii)).isVisible) + if (pdb.chains.elementAt(ii).isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; bsize += bonds.size(); @@ -537,7 +558,7 @@ public class PDBCanvas extends JPanel implements MouseListener, { for (int ii = 0; ii < pdb.chains.size(); ii++) { - chain = (PDBChain) pdb.chains.elementAt(ii); + chain = pdb.chains.elementAt(ii); for (int i = 0; i < chain.bonds.size(); i++) { @@ -751,7 +772,7 @@ public class PDBCanvas extends JPanel implements MouseListener, repaint(); if (foundchain != -1) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(foundchain); + PDBChain chain = pdb.chains.elementAt(foundchain); if (chain == mainchain) { if (fatom.alignmentMapping != -1) @@ -797,7 +818,7 @@ public class PDBCanvas extends JPanel implements MouseListener, PDBChain chain = null; if (foundchain != -1) { - chain = (PDBChain) pdb.chains.elementAt(foundchain); + chain = pdb.chains.elementAt(foundchain); if (chain == mainchain) { mouseOverStructure(fatom.resNumber, chain.id); @@ -840,18 +861,18 @@ public class PDBCanvas extends JPanel implements MouseListener, if ((evt.getModifiers() & Event.META_MASK) != 0) { - objmat.rotatez((float) ((mx - omx))); + objmat.rotatez(((mx - omx))); } else { - objmat.rotatex((float) ((my - omy))); - objmat.rotatey((float) ((omx - mx))); + objmat.rotatex(((my - omy))); + objmat.rotatey(((omx - mx))); } // Alter the bonds for (int ii = 0; ii < pdb.chains.size(); ii++) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -892,11 +913,11 @@ public class PDBCanvas extends JPanel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(ii); + PDBChain chain = pdb.chains.elementAt(ii); if (chain.isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -948,13 +969,13 @@ public class PDBCanvas extends JPanel implements MouseListener, for (int ii = 0; ii < pdb.chains.size(); ii++) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(ii); + PDBChain chain = pdb.chains.elementAt(ii); int truex; Bond tmpBond = null; if (chain.isVisible) { - Vector bonds = ((PDBChain) pdb.chains.elementAt(ii)).bonds; + Vector bonds = pdb.chains.elementAt(ii).bonds; for (int i = 0; i < bonds.size(); i++) { @@ -995,7 +1016,7 @@ public class PDBCanvas extends JPanel implements MouseListener, if (fatom != null) // )&& chain.ds != null) { - chain = (PDBChain) pdb.chains.elementAt(foundchain); + chain = pdb.chains.elementAt(foundchain); } } @@ -1060,7 +1081,7 @@ public class PDBCanvas extends JPanel implements MouseListener, { for (int ii = 0; ii < pdb.chains.size(); ii++) { - PDBChain chain = (PDBChain) pdb.chains.elementAt(ii); + PDBChain chain = pdb.chains.elementAt(ii); chain.isVisible = b; } mainchain.isVisible = true; @@ -1081,7 +1102,9 @@ public class PDBCanvas extends JPanel implements MouseListener, public void mouseOverStructure(int pdbResNum, String chain) { if (lastMessage == null || !lastMessage.equals(pdbResNum + chain)) + { ssm.mouseOverStructure(pdbResNum, chain, pdbentry.getFile()); + } lastMessage = pdbResNum + chain; } @@ -1090,19 +1113,42 @@ public class PDBCanvas extends JPanel implements MouseListener, StringBuffer eval = new StringBuffer(); - public void highlightAtom(int atomIndex, int pdbResNum, String chain, - String pdbfile) + /** + * Highlight the specified atoms in the structure. + * + * @param atoms + */ + @Override + public void highlightAtoms(List atoms) { if (!seqColoursReady) { return; } - if (highlightRes != null && highlightRes.contains((atomIndex - 1) + "")) + for (AtomSpec atom : atoms) { - return; + int atomIndex = atom.getAtomIndex(); + if (highlightRes != null + && highlightRes.contains((atomIndex - 1) + "")) + { + continue; + } + + highlightAtom(atomIndex); } + redrawneeded = true; + repaint(); + } + + /** + * Highlight the atom at the specified index. + * + * @param atomIndex + */ + protected void highlightAtom(int atomIndex) + { int index = -1; Bond tmpBond; for (index = 0; index < mainchain.bonds.size(); index++) @@ -1138,9 +1184,6 @@ public class PDBCanvas extends JPanel implements MouseListener, break; } } - - redrawneeded = true; - repaint(); } public Color getColour(int atomIndex, int pdbResNum, String chain, diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index 592fd4f..ba42621 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -32,6 +32,7 @@ import jalview.schemes.ResidueProperties; import jalview.structure.SelectionSource; import jalview.structure.SequenceListener; import jalview.structure.StructureSelectionManager; +import jalview.structure.VamsasSource; import jalview.util.MessageManager; import java.awt.BorderLayout; @@ -680,6 +681,12 @@ public class SeqPanel extends Panel implements MouseMotionListener, } + @Override + public VamsasSource getVamsasSource() + { + return this.ap == null ? null : this.ap.av; + } + public void updateColours(SequenceI seq, int index) { System.out.println("update the seqPanel colours"); diff --git a/src/jalview/commands/EditCommand.java b/src/jalview/commands/EditCommand.java index 82de3b2..ba01b1c 100644 --- a/src/jalview/commands/EditCommand.java +++ b/src/jalview/commands/EditCommand.java @@ -26,11 +26,16 @@ import jalview.datamodel.Annotation; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.util.ReverseListIterator; +import jalview.util.StringUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Map; /** * @@ -110,13 +115,88 @@ public class EditCommand implements CommandI } /** - * Add the given edit command to the stored list of commands. + * Add the given edit command to the stored list of commands. If simply + * expanding the range of the last command added, then modify it instead of + * adding a new command. * * @param e */ - protected void addEdit(Edit e) + public void addEdit(Edit e) { - edits.add(e); + if (!expandEdit(edits, e)) + { + edits.add(e); + } + } + + /** + * Returns true if the new edit is incorporated by updating (expanding the + * range of) the last edit on the list, else false. We can 'expand' the last + * edit if the new one is the same action, on the same sequences, and acts on + * a contiguous range. This is the case where a mouse drag generates a series + * of contiguous gap insertions or deletions. + * + * @param edits + * @param e + * @return + */ + protected static boolean expandEdit(List edits, Edit e) + { + if (edits == null || edits.isEmpty()) + { + return false; + } + Edit lastEdit = edits.get(edits.size() - 1); + Action action = e.command; + if (lastEdit.command != action) + { + return false; + } + + /* + * Both commands must act on the same sequences - compare the underlying + * dataset sequences, rather than the aligned sequences, which change as + * they are edited. + */ + if (lastEdit.seqs.length != e.seqs.length) + { + return false; + } + for (int i = 0; i < e.seqs.length; i++) + { + if (lastEdit.seqs[i].getDatasetSequence() != e.seqs[i] + .getDatasetSequence()) + { + return false; + } + } + + /** + * Check a contiguous edit; either + *
    + *
  • a new Insert positions to the right of the last , or
  • + *
  • a new Delete gaps which is positions to the left of the last + * delete.
  • + *
+ */ + boolean contiguous = (action == Action.INSERT_GAP && e.position == lastEdit.position + + lastEdit.number) + || (action == Action.DELETE_GAP && e.position + e.number == lastEdit.position); + if (contiguous) + { + /* + * We are just expanding the range of the last edit. For delete gap, also + * moving the start position left. + */ + lastEdit.number += e.number; + lastEdit.seqs = e.seqs; + if (action == Action.DELETE_GAP) + { + lastEdit.position--; + } + return true; + } + return false; } /** @@ -209,7 +289,32 @@ public class EditCommand implements CommandI edit.fullAlignmentHeight = true; } - edits.add(edit); + addEdit(edit); + + if (performEdit) + { + performEdit(edit, views); + } + } + + /** + * Overloaded method that accepts an Edit object with additional parameters. + * + * @param edit + * @param al + * @param performEdit + * @param views + */ + final public void appendEdit(Edit edit, AlignmentI al, + boolean performEdit, AlignmentI[] views) + { + if (al.getHeight() == edit.seqs.length) + { + edit.al = al; + edit.fullAlignmentHeight = true; + } + + addEdit(edit); if (performEdit) { @@ -223,7 +328,7 @@ public class EditCommand implements CommandI * @param commandIndex * @param views */ - final void performEdit(int commandIndex, AlignmentI[] views) + public final void performEdit(int commandIndex, AlignmentI[] views) { ListIterator iterator = edits.listIterator(commandIndex); while (iterator.hasNext()) @@ -239,7 +344,7 @@ public class EditCommand implements CommandI * @param edit * @param views */ - protected void performEdit(Edit edit, AlignmentI[] views) + protected static void performEdit(Edit edit, AlignmentI[] views) { switch (edit.command) { @@ -316,13 +421,13 @@ public class EditCommand implements CommandI * * @param command */ - final private void insertGap(Edit command) + final private static void insertGap(Edit command) { for (int s = 0; s < command.seqs.length; s++) { - command.seqs[s].insertCharAt(command.position, command.number, - command.gapChar); + command.seqs[s].insertCharAt(command.position, + command.number, command.gapChar); // System.out.println("pos: "+command.position+" number: "+command.number); } @@ -348,7 +453,7 @@ public class EditCommand implements CommandI * * @param command */ - final private void deleteGap(Edit command) + final static private void deleteGap(Edit command) { for (int s = 0; s < command.seqs.length; s++) { @@ -366,7 +471,7 @@ public class EditCommand implements CommandI * @param command * @param views */ - void cut(Edit command, AlignmentI[] views) + static void cut(Edit command, AlignmentI[] views) { boolean seqDeleted = false; command.string = new char[command.seqs.length][]; @@ -430,7 +535,7 @@ public class EditCommand implements CommandI * @param command * @param views */ - void paste(Edit command, AlignmentI[] views) + static void paste(Edit command, AlignmentI[] views) { StringBuffer tmp; boolean newDSNeeded; @@ -548,7 +653,7 @@ public class EditCommand implements CommandI command.string = null; } - void replace(Edit command) + static void replace(Edit command) { StringBuffer tmp; String oldstring; @@ -625,7 +730,7 @@ public class EditCommand implements CommandI } } - final void adjustAnnotations(Edit command, boolean insert, + final static void adjustAnnotations(Edit command, boolean insert, boolean modifyVisibility, AlignmentI[] views) { AlignmentAnnotation[] annotations = null; @@ -949,7 +1054,7 @@ public class EditCommand implements CommandI } } - final void adjustFeatures(Edit command, int index, int i, int j, + final static void adjustFeatures(Edit command, int index, int i, int j, boolean insert) { SequenceI seq = command.seqs[index]; @@ -1027,7 +1132,110 @@ public class EditCommand implements CommandI } - class Edit + /** + * Returns the list of edit commands wrapped by this object. + * + * @return + */ + public List getEdits() + { + return this.edits; + } + + /** + * Returns a map whose keys are the dataset sequences, and values their + * aligned sequences before the command edit list was applied. The aligned + * sequences are copies, which may be updated without affecting the originals. + * + * The command holds references to the aligned sequences (after editing). If + * the command is an 'undo',then the prior state is simply the aligned state. + * Otherwise, we have to derive the prior state by working backwards through + * the edit list to infer the aligned sequences before editing. + * + * Note: an alternative solution would be to cache the 'before' state of each + * edit, but this would be expensive in space in the common case that the + * original is never needed (edits are not mirrored). + * + * @return + * @throws IllegalStateException + * on detecting an edit command of a type that can't be unwound + */ + public Map priorState(boolean forUndo) + { + Map result = new HashMap(); + if (getEdits() == null) + { + return result; + } + if (forUndo) + { + for (Edit e : getEdits()) + { + for (SequenceI seq : e.getSequences()) + { + SequenceI ds = seq.getDatasetSequence(); + SequenceI preEdit = result.get(ds); + if (preEdit == null) + { + preEdit = new Sequence("", seq.getSequenceAsString()); + preEdit.setDatasetSequence(ds); + result.put(ds, preEdit); + } + } + } + return result; + } + + /* + * Work backwards through the edit list, deriving the sequences before each + * was applied. The final result is the sequence set before any edits. + */ + Iterator edits = new ReverseListIterator(getEdits()); + while (edits.hasNext()) + { + Edit oldEdit = edits.next(); + Action action = oldEdit.getAction(); + int position = oldEdit.getPosition(); + int number = oldEdit.getNumber(); + final char gap = oldEdit.getGapCharacter(); + for (SequenceI seq : oldEdit.getSequences()) + { + SequenceI ds = seq.getDatasetSequence(); + SequenceI preEdit = result.get(ds); + if (preEdit == null) + { + preEdit = new Sequence("", seq.getSequenceAsString()); + preEdit.setDatasetSequence(ds); + result.put(ds, preEdit); + } + /* + * 'Undo' this edit action on the sequence (updating the value in the + * map). + */ + if (ds != null) + { + if (action == Action.DELETE_GAP) + { + preEdit.setSequence(new String(StringUtils.insertCharAt( + preEdit.getSequence(), position, + number, gap))); + } + else if (action == Action.INSERT_GAP) + { + preEdit.setSequence(new String(StringUtils.deleteChars( + preEdit.getSequence(), position, position + number))); + } + else + { + throw new IllegalStateException("Can't undo edit action " + action); + } + } + } + } + return result; + } + + public class Edit { public SequenceI[] oldds; @@ -1053,7 +1261,7 @@ public class EditCommand implements CommandI char gapChar; - Edit(Action command, SequenceI[] seqs, int position, int number, + public Edit(Action command, SequenceI[] seqs, int position, int number, char gapChar) { this.command = command; @@ -1099,5 +1307,49 @@ public class EditCommand implements CommandI fullAlignmentHeight = (al.getHeight() == seqs.length); } + + public SequenceI[] getSequences() + { + return seqs; + } + + public int getPosition() + { + return position; + } + + public Action getAction() + { + return command; + } + + public int getNumber() + { + return number; + } + + public char getGapCharacter() + { + return gapChar; + } + } + + /** + * Returns an iterator over the list of edit commands which traverses the list + * either forwards or backwards. + * + * @param forwards + * @return + */ + public Iterator getEditIterator(boolean forwards) + { + if (forwards) + { + return getEdits().iterator(); + } + else + { + return new ReverseListIterator(getEdits()); + } } } diff --git a/src/jalview/datamodel/SearchResults.java b/src/jalview/datamodel/SearchResults.java index 8434e81..6b7a3eb 100755 --- a/src/jalview/datamodel/SearchResults.java +++ b/src/jalview/datamodel/SearchResults.java @@ -193,4 +193,14 @@ public class SearchResults this.end = end; } } + + /** + * Returns true if no search result matches are held. + * + * @return + */ + public boolean isEmpty() + { + return (matches == null) || (matches.length == 0); + } } diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index 0652fb5..6b0e9fc 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -21,6 +21,7 @@ package jalview.datamodel; import jalview.analysis.AlignSeq; +import jalview.util.StringUtils; import java.util.ArrayList; import java.util.Enumeration; @@ -614,6 +615,14 @@ public class Sequence implements SequenceI } } + /** + * Returns the (1-based) residue position for a (0-based) alignment position + * + * @param i + * column index in alignment (from 0..= sequence.length) + if (from >= sequence.length) { return; } - - char[] tmp; - - if (j >= sequence.length) - { - tmp = new char[i]; - System.arraycopy(sequence, 0, tmp, 0, i); - j = sequence.length; - } - else + if (to > sequence.length) { - tmp = new char[sequence.length - j + i]; - System.arraycopy(sequence, 0, tmp, 0, i); - System.arraycopy(sequence, j, tmp, i, sequence.length - j); + to = sequence.length; } + char[] tmp = StringUtils.deleteChars(sequence, from, to); + + recreateDatasetAfterDeletions(from, to); + sequence = tmp; + } + + /** + * Reconstruct the dataset sequence, if necessary, after deletion of columns + * in the aligned sequence. + * + * @param from + * start column of deletions (zero-based, inclusive) + * @param to + * end column of deletions (exclusive) + */ + protected boolean recreateDatasetAfterDeletions(int from, int to) + { + boolean created = false; boolean createNewDs = false; + int newstart = start, newend = end; // TODO: take a look at the new dataset creation validation method below - - // this could become time comsuming for large sequences - consider making it + // this could become time consuming for large sequences - consider making it // more efficient - for (int s = i; s < j; s++) + for (int s = from; s < to; s++) { if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23) { @@ -730,17 +749,17 @@ public class Sequence implements SequenceI if (sindex == s) { // delete characters including start of sequence - newstart = findPosition(j); + newstart = findPosition(to); break; // don't need to search for any more residue characters. } else { // delete characters after start. int eindex = findIndex(end) - 1; - if (eindex < j) + if (eindex < to) { // delete characters at end of sequence - newend = findPosition(i - 1); + newend = findPosition(from - 1); break; // don't need to search for any more residue characters. } else @@ -760,56 +779,25 @@ public class Sequence implements SequenceI Sequence ds = new Sequence(datasetSequence); // TODO: remove any non-inheritable properties ? // TODO: create a sequence mapping (since there is a relation here ?) - ds.deleteChars(i, j); + ds.deleteChars(from, to); datasetSequence = ds; + created = true; } start = newstart; end = newend; - sequence = tmp; + return created; } - /** - * DOCUMENT ME! - * - * @param i - * DOCUMENT ME! - * @param c - * DOCUMENT ME! - * @param chop - * DOCUMENT ME! - */ - public void insertCharAt(int i, int length, char c) + @Override + public void insertCharAt(int i, char c) { - char[] tmp = new char[sequence.length + length]; - - if (i >= sequence.length) - { - System.arraycopy(sequence, 0, tmp, 0, sequence.length); - i = sequence.length; - } - else - { - System.arraycopy(sequence, 0, tmp, 0, i); - } - - int index = i; - while (length > 0) - { - tmp[index++] = c; - length--; - } - - if (i < sequence.length) - { - System.arraycopy(sequence, i, tmp, index, sequence.length - i); - } - - sequence = tmp; + insertCharAt(i, 1, c); } - public void insertCharAt(int i, char c) + @Override + public void insertCharAt(int pos, int count, char c) { - insertCharAt(i, 1, c); + sequence = StringUtils.insertCharAt(sequence, pos, count, c); } public String getVamsasId() @@ -1010,7 +998,7 @@ public class Sequence implements SequenceI AlignmentAnnotation _aa = new AlignmentAnnotation(aa); _aa.sequenceRef = datasetSequence; _aa.adjustForAlignment(); // uses annotation's own record of - // sequence-column mapping + // sequence-column mapping datasetSequence.addAlignmentAnnotation(_aa); } } @@ -1247,8 +1235,10 @@ public class Sequence implements SequenceI String label) { List result = new ArrayList(); - if (this.annotation != null) { - for (AlignmentAnnotation ann : annotation) { + if (this.annotation != null) + { + for (AlignmentAnnotation ann : annotation) + { if (ann.calcId != null && ann.calcId.equals(calcId) && ann.label != null && ann.label.equals(label)) { diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index fc67efd..3ca759d 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -220,9 +220,9 @@ public interface SequenceI * if necessary and adjusting start and end positions accordingly. * * @param i - * first column in range to delete + * first column in range to delete (inclusive) * @param j - * last column in range to delete + * last column in range to delete (exclusive) */ public void deleteChars(int i, int j); @@ -238,13 +238,12 @@ public interface SequenceI /** * DOCUMENT ME! - * - * @param i + * @param position * DOCUMENT ME! - * @param c + * @param ch * DOCUMENT ME! */ - public void insertCharAt(int i, int length, char c); + public void insertCharAt(int position, int count, char ch); /** * DOCUMENT ME! diff --git a/src/jalview/ext/jmol/JalviewJmolBinding.java b/src/jalview/ext/jmol/JalviewJmolBinding.java index 9187912..921e521 100644 --- a/src/jalview/ext/jmol/JalviewJmolBinding.java +++ b/src/jalview/ext/jmol/JalviewJmolBinding.java @@ -32,6 +32,7 @@ import jalview.datamodel.SequenceI; import jalview.io.AppletFormatAdapter; import jalview.schemes.ColourSchemeI; import jalview.schemes.ResidueProperties; +import jalview.structure.AtomSpec; import jalview.structure.StructureListener; import jalview.structure.StructureMapping; import jalview.structure.StructureSelectionManager; @@ -47,6 +48,7 @@ import java.net.URL; import java.security.AccessControlException; import java.util.Enumeration; import java.util.Hashtable; +import java.util.List; import java.util.Map; import java.util.Vector; @@ -232,7 +234,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i + (1 + getModelNum((String) chainFile.get(lbl))) + " or "); } if (cmd.length() > 0) + { cmd.setLength(cmd.length() - 4); + } evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd); } @@ -630,7 +634,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i jalview.api.AlignmentViewPanel alignmentv) { if (!colourBySequence || !isLoadingFinished()) + { return; + } if (ssm == null) { return; @@ -649,10 +655,12 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i for (jalview.structure.StructureMappingcommandSet cpdbbyseq : JmolCommands .getColourBySequenceCommand(ssm, files, sequence, sr, fr, alignment)) + { for (String cbyseq : cpdbbyseq.commands) { evalStateCommand(cbyseq); } + } } public boolean isColourBySequence() @@ -702,7 +710,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i String pdbfile) { if (getModelNum(pdbfile) < 0) + { return null; + } // TODO: verify atomIndex is selecting correct model. return new Color(viewer.getAtomArgb(atomIndex)); } @@ -735,7 +745,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i for (int i = 0; i < mfn.length; i++) { if (mfn[i].equalsIgnoreCase(modelFileName)) + { return i; + } } return -1; } @@ -831,64 +843,76 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i jmolpopup.show(x, y); } - // jmol/ssm only - public void highlightAtom(int atomIndex, int pdbResNum, String chain, - String pdbfile) + /** + * Highlight the specified atoms in the structure. + * + * @param atoms + */ + @Override + public void highlightAtoms(List atoms) { if (modelFileNames == null) { return; } - // look up file model number for this pdbfile - int mdlNum = 0; - String fn; - // may need to adjust for URLencoding here - we don't worry about that yet. - while (mdlNum < modelFileNames.length - && !pdbfile.equals(modelFileNames[mdlNum])) - { - // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn); - mdlNum++; - } - if (mdlNum == modelFileNames.length) + for (AtomSpec atom : atoms) { - return; - } + String pdbfile = atom.getPdbId(); + int pdbResNum = atom.getPdbResNum(); + String chain = atom.getChain(); + + // look up file model number for this pdbfile + int mdlNum = 0; + String fn; + // may need to adjust for URLencoding here - we don't worry about that + // yet. + while (mdlNum < modelFileNames.length + && !pdbfile.equals(modelFileNames[mdlNum])) + { + // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn); + mdlNum++; + } + if (mdlNum == modelFileNames.length) + { + return; + } - jmolHistory(false); - // if (!pdbfile.equals(pdbentry.getFile())) - // return; - if (resetLastRes.length() > 0) - { - viewer.evalStringQuiet(resetLastRes.toString()); - } + jmolHistory(false); + // if (!pdbfile.equals(pdbentry.getFile())) + // return; + if (resetLastRes.length() > 0) + { + viewer.evalStringQuiet(resetLastRes.toString()); + } - eval.setLength(0); - eval.append("select " + pdbResNum); // +modelNum + eval.setLength(0); + eval.append("select " + pdbResNum); // +modelNum - resetLastRes.setLength(0); - resetLastRes.append("select " + pdbResNum); // +modelNum + resetLastRes.setLength(0); + resetLastRes.append("select " + pdbResNum); // +modelNum - eval.append(":"); - resetLastRes.append(":"); - if (!chain.equals(" ")) - { - eval.append(chain); - resetLastRes.append(chain); - } - { - eval.append(" /" + (mdlNum + 1)); - resetLastRes.append("/" + (mdlNum + 1)); - } - eval.append(";wireframe 100;" + eval.toString() + " and not hetero;"); + eval.append(":"); + resetLastRes.append(":"); + if (!chain.equals(" ")) + { + eval.append(chain); + resetLastRes.append(chain); + } + { + eval.append(" /" + (mdlNum + 1)); + resetLastRes.append("/" + (mdlNum + 1)); + } + eval.append(";wireframe 100;" + eval.toString() + " and not hetero;"); - resetLastRes.append(";wireframe 0;" + resetLastRes.toString() - + " and not hetero; spacefill 0;"); + resetLastRes.append(";wireframe 0;" + resetLastRes.toString() + + " and not hetero; spacefill 0;"); - eval.append("spacefill 200;select none"); + eval.append("spacefill 200;select none"); - viewer.evalStringQuiet(eval.toString()); - jmolHistory(true); + viewer.evalStringQuiet(eval.toString()); + jmolHistory(true); + } } @@ -944,8 +968,10 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i String chainId; if (strInfo.indexOf(":") > -1) + { chainId = strInfo.substring(strInfo.indexOf(":") + 1, strInfo.indexOf(".")); + } else { chainId = " "; @@ -983,7 +1009,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i ; } if (lastMessage == null || !lastMessage.equals(strInfo)) + { ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename); + } lastMessage = strInfo; } @@ -1017,13 +1045,17 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i int chainSeparator = strInfo.indexOf(":"); int p = 0; if (chainSeparator == -1) + { chainSeparator = strInfo.indexOf("."); + } String picked = strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator); String mdlString = ""; if ((p = strInfo.indexOf(":")) > -1) + { picked += strInfo.substring(p + 1, strInfo.indexOf(".")); + } if ((p = strInfo.indexOf("/")) > -1) { @@ -1271,7 +1303,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i for (int i = 0; i < pdb.chains.size(); i++) { String chid = new String(pdb.id + ":" - + ((MCview.PDBChain) pdb.chains.elementAt(i)).id); + + pdb.chains.elementAt(i).id); chainFile.put(chid, fileName); chainNames.addElement(chid); } @@ -1364,7 +1396,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i colourBySequence = false; if (cs == null) + { return; + } String res; int index; @@ -1378,7 +1412,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i res = en.nextElement().toString(); index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue(); if (index > 20) + { continue; + } col = cs.findColour(ResidueProperties.aa[index].charAt(0)); diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index 82f5e5c..7230baf 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -32,6 +32,7 @@ import jalview.datamodel.SequenceI; import jalview.io.AppletFormatAdapter; import jalview.schemes.ColourSchemeI; import jalview.schemes.ResidueProperties; +import jalview.structure.AtomSpec; import jalview.structure.StructureListener; import jalview.structure.StructureMapping; import jalview.structure.StructureSelectionManager; @@ -913,44 +914,56 @@ public abstract class JalviewChimeraBinding extends public abstract SequenceRenderer getSequenceRenderer( AlignmentViewPanel alignment); - // jmol/ssm only - public void highlightAtom(int atomIndex, int pdbResNum, String chain, - String pdbfile) + /** + * Highlight the specified atom positions in the structure. + * + * @param atomIndex + * @param pdbResNum + * @param chain + * @param pdbfile + */ + @Override + public void highlightAtoms(List atoms) { - List cms = chimmaps.get(pdbfile); - if (cms != null) + for (AtomSpec atom : atoms) { - int mdlNum = cms.get(0).getModelNumber(); - - viewerCommandHistory(false); - // viewer.stopListening(); - if (resetLastRes.length() > 0) + int pdbResNum = atom.getPdbResNum(); + String chain = atom.getChain(); + List cms = chimmaps.get(atom.getPdbId()); + if (cms != null) { - eval.setLength(0); - eval.append(resetLastRes.toString() + ";"); - } + int mdlNum = cms.get(0).getModelNumber(); + + viewerCommandHistory(false); + // viewer.stopListening(); + if (resetLastRes.length() > 0) + { + eval.setLength(0); + eval.append(resetLastRes.toString() + ";"); + } - eval.append("display "); // +modelNum + eval.append("display "); // +modelNum - resetLastRes.setLength(0); - resetLastRes.append("~display "); - { - eval.append(" #" + (mdlNum)); - resetLastRes.append(" #" + (mdlNum)); - } - // complete select string + resetLastRes.setLength(0); + resetLastRes.append("~display "); + { + eval.append(" #" + (mdlNum)); + resetLastRes.append(" #" + (mdlNum)); + } + // complete select string - eval.append(":" + pdbResNum); - resetLastRes.append(":" + pdbResNum); - if (!chain.equals(" ")) - { - eval.append("." + chain); - resetLastRes.append("." + chain); + eval.append(":" + pdbResNum); + resetLastRes.append(":" + pdbResNum); + if (!chain.equals(" ")) + { + eval.append("." + chain); + resetLastRes.append("." + chain); + } + + viewer.sendChimeraCommand(eval.toString(), false); + viewerCommandHistory(true); + // viewer.startListening(); } - - viewer.sendChimeraCommand(eval.toString(), false); - viewerCommandHistory(true); - // viewer.startListening(); } } diff --git a/src/jalview/ext/varna/JalviewVarnaBinding.java b/src/jalview/ext/varna/JalviewVarnaBinding.java index 6a32f30..12a94d9 100644 --- a/src/jalview/ext/varna/JalviewVarnaBinding.java +++ b/src/jalview/ext/varna/JalviewVarnaBinding.java @@ -21,6 +21,7 @@ package jalview.ext.varna; import java.awt.event.*; +import java.util.List; import jalview.api.SequenceStructureBinding; import jalview.api.StructureSelectionManagerProvider; @@ -33,4 +34,9 @@ public abstract class JalviewVarnaBinding extends SequenceStructureBindingModel { + @Override + public void highlightAtoms(List atoms) + { + } + } diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 50043e6..865a9aa 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -1410,10 +1410,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, void updateEditMenuBar() { - if (viewport.historyList.size() > 0) + if (viewport.getHistoryList().size() > 0) { undoMenuItem.setEnabled(true); - CommandI command = viewport.historyList.peek(); + CommandI command = viewport.getHistoryList().peek(); undoMenuItem.setText(MessageManager.formatMessage( "label.undo_command", new String[] { command.getDescription() })); @@ -1424,11 +1424,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, undoMenuItem.setText(MessageManager.getString("action.undo")); } - if (viewport.redoList.size() > 0) + if (viewport.getRedoList().size() > 0) { redoMenuItem.setEnabled(true); - CommandI command = viewport.redoList.peek(); + CommandI command = viewport.getRedoList().peek(); redoMenuItem.setText(MessageManager.formatMessage( "label.redo_command", new String[] { command.getDescription() })); @@ -1444,8 +1444,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { if (command.getSize() > 0) { - viewport.historyList.push(command); - viewport.redoList.clear(); + viewport.addToHistoryList(command); + viewport.clearRedoList(); updateEditMenuBar(); viewport.updateHiddenColumns(); // viewport.hasHiddenColumns = (viewport.getColumnSelection() != null @@ -1488,12 +1488,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override protected void undoMenuItem_actionPerformed(ActionEvent e) { - if (viewport.historyList.empty()) + if (viewport.getHistoryList().isEmpty()) { return; } - CommandI command = viewport.historyList.pop(); - viewport.redoList.push(command); + CommandI command = viewport.getHistoryList().pop(); + viewport.addToRedoList(command); + // TODO: execute command before adding to redo list / broadcasting? command.undoCommand(getViewAlignments()); AlignViewport originalSource = getOriginatingSource(command); @@ -1526,13 +1527,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override protected void redoMenuItem_actionPerformed(ActionEvent e) { - if (viewport.redoList.size() < 1) + if (viewport.getRedoList().size() < 1) { return; } - CommandI command = viewport.redoList.pop(); - viewport.historyList.push(command); + CommandI command = viewport.getRedoList().pop(); + viewport.addToHistoryList(command); command.doCommand(getViewAlignments()); AlignViewport originalSource = getOriginatingSource(command); @@ -1702,11 +1703,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } boolean appendHistoryItem = false; - if (viewport.historyList != null && viewport.historyList.size() > 0 - && viewport.historyList.peek() instanceof SlideSequencesCommand) + if (viewport.getHistoryList() != null + && viewport.getHistoryList().size() > 0 + && viewport.getHistoryList().peek() instanceof SlideSequencesCommand) { appendHistoryItem = ssc - .appendSlideCommand((SlideSequencesCommand) viewport.historyList + .appendSlideCommand((SlideSequencesCommand) viewport + .getHistoryList() .peek()); } @@ -2600,6 +2603,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, new Finder(); } + /** + * Create a new view of the current alignment. + */ @Override public void newView_actionPerformed(ActionEvent e) { @@ -2662,8 +2668,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, viewport.viewName = "Original"; } - newap.av.historyList = viewport.historyList; - newap.av.redoList = viewport.redoList; + newap.av.setHistoryList(viewport.getHistoryList()); + newap.av.setRedoList(viewport.getRedoList()); int index = Desktop.getViewCount(viewport.getSequenceSetId()); // make sure the new view has a unique name - this is essential for Jalview @@ -4806,6 +4812,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } } + /** + * Construct and display a new frame containing the translation of this + * frame's cDNA sequences to their protein (amino acid) equivalents. + */ @Override public void showTranslation_actionPerformed(ActionEvent e) { @@ -4855,6 +4865,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, Desktop.addInternalFrame(af, MessageManager.formatMessage( "label.translation_of_params", new String[] { this.getTitle() }), DEFAULT_WIDTH, DEFAULT_HEIGHT); + viewport.getStructureSelectionManager().addCommandListener(viewport); } } diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index d24f6c4..ff3e329 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -43,6 +43,7 @@ import jalview.analysis.NJTree; import jalview.api.AlignViewportI; import jalview.bin.Cache; import jalview.commands.CommandI; +import jalview.commands.EditCommand; import jalview.datamodel.AlignmentI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.PDBEntry; @@ -51,6 +52,7 @@ import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.schemes.ColourSchemeProperty; import jalview.schemes.UserColourScheme; +import jalview.structure.CommandListener; import jalview.structure.SelectionSource; import jalview.structure.StructureSelectionManager; import jalview.structure.VamsasSource; @@ -61,9 +63,11 @@ import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.Rectangle; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.Hashtable; -import java.util.Stack; +import java.util.List; import java.util.Vector; /** @@ -73,7 +77,7 @@ import java.util.Vector; * @version $Revision: 1.141 $ */ public class AlignViewport extends AlignmentViewport implements - SelectionSource, VamsasSource, AlignViewportI + SelectionSource, VamsasSource, AlignViewportI, CommandListener { int startRes; @@ -139,9 +143,9 @@ public class AlignViewport extends AlignmentViewport implements boolean gatherViewsHere = false; - Stack historyList = new Stack(); + private Deque historyList = new ArrayDeque(); - Stack redoList = new Stack(); + private Deque redoList = new ArrayDeque(); int thresholdTextColour = 0; @@ -1293,4 +1297,112 @@ public class AlignViewport extends AlignmentViewport implements { this.showAutocalculatedAbove = showAutocalculatedAbove; } + + /** + * Method called when another alignment's edit (or possibly other) command is + * broadcast to here. + * + * To allow for sequence mappings (e.g. protein to cDNA), we have to first + * 'unwind' the command on the source sequences (in simulation, not in fact), + * and then for each edit in turn: + *
    + *
  • compute the equivalent edit on the mapped sequences
  • + *
  • apply the mapped edit
  • + *
  • 'apply' the source edit to the working copy of the source sequences
  • + *
+ * + * @param command + * @param undo + * @param ssm + */ + @Override + public void mirrorCommand(CommandI command, boolean undo, + StructureSelectionManager ssm) + { + /* + * Only EditCommand is currently handled by listeners. + */ + if (!(command instanceof EditCommand)) + { + return; + } + EditCommand edit = (EditCommand) command; + + List seqs = getAlignment().getSequences(); + EditCommand mappedCommand = ssm.mapEditCommand(edit, undo, seqs, + getGapCharacter()); + AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments(); + mappedCommand.performEdit(0, views); + getAlignPanel().alignmentChanged(); + } + + @Override + public VamsasSource getVamsasSource() + { + return this; + } + + /** + * Add one command to the command history list. + * + * @param command + */ + public void addToHistoryList(CommandI command) + { + if (this.historyList != null) + { + this.historyList.push(command); + broadcastCommand(command, false); + } + } + + protected void broadcastCommand(CommandI command, boolean undo) + { + getStructureSelectionManager().commandPerformed(command, undo, getVamsasSource()); + } + + /** + * Add one command to the command redo list. + * + * @param command + */ + public void addToRedoList(CommandI command) + { + if (this.redoList != null) + { + this.redoList.push(command); + } + broadcastCommand(command, true); + } + + /** + * Clear the command redo list. + */ + public void clearRedoList() + { + if (this.redoList != null) + { + this.redoList.clear(); + } + } + + public void setHistoryList(Deque list) + { + this.historyList = list; + } + + public Deque getHistoryList() + { + return this.historyList; + } + + public void setRedoList(Deque list) + { + this.redoList = list; + } + + public Deque getRedoList() + { + return this.redoList; + } } diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index 2398bda..fc36ebb 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -1464,6 +1464,7 @@ public class AlignmentPanel extends GAlignmentPanel implements .getStructureSelectionManager(); ssm.removeStructureViewerListener(seqPanel, null); ssm.removeSelectionListener(seqPanel); + ssm.removeEditListener(av); av.setAlignment(null); av = null; } diff --git a/src/jalview/gui/AppVarnaBinding.java b/src/jalview/gui/AppVarnaBinding.java index a738a2c..b68c3d7 100644 --- a/src/jalview/gui/AppVarnaBinding.java +++ b/src/jalview/gui/AppVarnaBinding.java @@ -875,14 +875,6 @@ public class AppVarnaBinding extends jalview.ext.varna.JalviewVarnaBinding } @Override - public Color getColour(int atomIndex, int pdbResNum, String chain, - String pdbId) - { - // TODO Auto-generated method stub - return null; - } - - @Override public String[] getPdbFile() { // TODO Auto-generated method stub @@ -890,21 +882,6 @@ public class AppVarnaBinding extends jalview.ext.varna.JalviewVarnaBinding } @Override - public void highlightAtom(int atomIndex, int pdbResNum, String chain, - String pdbId) - { - // TODO Auto-generated method stub - - } - - @Override - public void mouseOverStructure(int atomIndex, String strInfo) - { - // TODO Auto-generated method stub - - } - - @Override public void releaseReferences(Object svl) { // TODO Auto-generated method stub diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index cccf34d..8b74620 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -3421,8 +3421,8 @@ public class Jalview2XML if (av != null) { // propagate shared settings to this new view - af.viewport.historyList = av.historyList; - af.viewport.redoList = av.redoList; + af.viewport.setHistoryList(av.getHistoryList()); + af.viewport.setRedoList(av.getRedoList()); } else { diff --git a/src/jalview/gui/RedundancyPanel.java b/src/jalview/gui/RedundancyPanel.java index ab0a0b8..25e9d19 100755 --- a/src/jalview/gui/RedundancyPanel.java +++ b/src/jalview/gui/RedundancyPanel.java @@ -276,10 +276,10 @@ public class RedundancyPanel extends GSliderPanel implements Runnable } CommandI command = historyList.pop(); - if (ap.av.historyList.contains(command)) + if (ap.av.getHistoryList().contains(command)) { command.undoCommand(af.getViewAlignments()); - ap.av.historyList.remove(command); + ap.av.getHistoryList().remove(command); ap.av.firePropertyChange("alignment", null, ap.av.getAlignment().getSequences()); af.updateEditMenuBar(); } diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index c10a4a9..2c9b7f6 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -22,6 +22,7 @@ package jalview.gui; import jalview.commands.EditCommand; import jalview.commands.EditCommand.Action; +import jalview.commands.EditCommand.Edit; import jalview.datamodel.ColumnSelection; import jalview.datamodel.SearchResults; import jalview.datamodel.Sequence; @@ -34,6 +35,8 @@ import jalview.structure.SelectionListener; import jalview.structure.SelectionSource; import jalview.structure.SequenceListener; import jalview.structure.StructureSelectionManager; +import jalview.structure.VamsasSource; +import jalview.util.Comparison; import jalview.util.MessageManager; import java.awt.BorderLayout; @@ -281,22 +284,33 @@ public class SeqPanel extends JPanel implements MouseListener, return features; } + /** + * When all of a sequence of edits are complete, put the resulting edit list + * on the history stack (undo list), and reset flags for editing in progress. + */ void endEditing() { - if (editCommand != null && editCommand.getSize() > 0) + try + { + if (editCommand != null && editCommand.getSize() > 0) + { + ap.alignFrame.addHistoryItem(editCommand); + av.firePropertyChange("alignment", null, av.getAlignment() + .getSequences()); + } + } finally { - ap.alignFrame.addHistoryItem(editCommand); - av.firePropertyChange("alignment", null, av.getAlignment() - .getSequences()); + /* + * Tidy up come what may... + */ + startseq = -1; + lastres = -1; + editingSeqs = false; + groupEditing = false; + keyboardNo1 = null; + keyboardNo2 = null; + editCommand = null; } - - startseq = -1; - lastres = -1; - editingSeqs = false; - groupEditing = false; - keyboardNo1 = null; - keyboardNo2 = null; - editCommand = null; } void setCursorRow() @@ -649,7 +663,6 @@ public class SeqPanel extends JPanel implements MouseListener, String lastMessage; - @Override public void mouseOverSequence(SequenceI sequence, int index, int pos) { String tmp = sequence.hashCode() + " " + index + " " + pos; @@ -676,6 +689,10 @@ public class SeqPanel extends JPanel implements MouseListener, } @Override + public VamsasSource getVamsasSource() + { + return this.ap == null ? null : this.ap.av; + } public void updateColours(SequenceI seq, int index) { System.out.println("update the seqPanel colours"); @@ -962,7 +979,7 @@ public class SeqPanel extends JPanel implements MouseListener, } } - StringBuffer message = new StringBuffer(); + StringBuilder message = new StringBuilder(64); if (groupEditing) { message.append("Edit group:"); @@ -1187,8 +1204,8 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - editCommand.appendEdit(Action.INSERT_GAP, groupSeqs, - startres, startres - lastres, av.getAlignment(), true); + appendEdit(Action.INSERT_GAP, groupSeqs, startres, startres + - lastres); } } else @@ -1203,8 +1220,8 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - editCommand.appendEdit(Action.DELETE_GAP, groupSeqs, - startres, lastres - startres, av.getAlignment(), true); + appendEdit(Action.DELETE_GAP, groupSeqs, startres, lastres + - startres); } } @@ -1225,8 +1242,8 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[] - { seq }, lastres, startres - lastres, av.getAlignment(), true); + appendEdit(Action.INSERT_GAP, new SequenceI[] + { seq }, lastres, startres - lastres); } } else @@ -1238,7 +1255,7 @@ public class SeqPanel extends JPanel implements MouseListener, { for (int j = lastres; j > startres; j--) { - if (!jalview.util.Comparison.isGap(seq.getCharAt(startres))) + if (!Comparison.isGap(seq.getCharAt(startres))) { endEditing(); break; @@ -1253,7 +1270,7 @@ public class SeqPanel extends JPanel implements MouseListener, int max = 0; for (int m = startres; m < lastres; m++) { - if (!jalview.util.Comparison.isGap(seq.getCharAt(m))) + if (!Comparison.isGap(seq.getCharAt(m))) { break; } @@ -1262,9 +1279,8 @@ public class SeqPanel extends JPanel implements MouseListener, if (max > 0) { - editCommand.appendEdit(Action.DELETE_GAP, - new SequenceI[] - { seq }, startres, max, av.getAlignment(), true); + appendEdit(Action.DELETE_GAP, new SequenceI[] + { seq }, startres, max); } } } @@ -1280,8 +1296,8 @@ public class SeqPanel extends JPanel implements MouseListener, } else { - editCommand.appendEdit(Action.INSERT_NUC, new SequenceI[] - { seq }, lastres, startres - lastres, av.getAlignment(), true); + appendEdit(Action.INSERT_NUC, new SequenceI[] + { seq }, lastres, startres - lastres); } } } @@ -1316,22 +1332,37 @@ public class SeqPanel extends JPanel implements MouseListener, } } - editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1, - av.getAlignment(), true); + appendEdit(Action.DELETE_GAP, seq, blankColumn, 1); + + appendEdit(Action.INSERT_GAP, seq, j, 1); + + } + + /** + * Helper method to add and perform one edit action. + * + * @param action + * @param seq + * @param pos + * @param count + */ + protected void appendEdit(Action action, SequenceI[] seq, int pos, + int count) + { - editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1, - av.getAlignment(), true); + final Edit edit = new EditCommand().new Edit(action, seq, pos, count, + av.getAlignment().getGapCharacter()); + editCommand.appendEdit(edit, av.getAlignment(), + true, null); } void deleteChar(int j, SequenceI[] seq, int fixedColumn) { - editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1, - av.getAlignment(), true); + appendEdit(Action.DELETE_GAP, seq, j, 1); - editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1, - av.getAlignment(), true); + appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1); } /** diff --git a/src/jalview/gui/VamsasApplication.java b/src/jalview/gui/VamsasApplication.java index 77a7693..56feef5 100644 --- a/src/jalview/gui/VamsasApplication.java +++ b/src/jalview/gui/VamsasApplication.java @@ -347,7 +347,9 @@ public class VamsasApplication implements SelectionSource, VamsasSource public void end_session(boolean promptUser) { if (!inSession()) + { throw new Error(MessageManager.getString("error.jalview_no_connected_vamsas_session")); + } Cache.log.info("Jalview disconnecting from the Vamsas Session."); try { @@ -958,11 +960,14 @@ public class VamsasApplication implements SelectionSource, VamsasSource int i = -1; - public void mouseOver(SequenceI seq, int index, + @Override + public void mouseOverSequence(SequenceI seq, int index, VamsasSource source) { if (jv2vobj == null) + { return; + } if (seq != last || i != index) { VorbaId v = (VorbaId) jv2vobj.get(seq); diff --git a/src/jalview/javascript/MouseOverListener.java b/src/jalview/javascript/MouseOverListener.java index 19f7b11..2b0aab2 100644 --- a/src/jalview/javascript/MouseOverListener.java +++ b/src/jalview/javascript/MouseOverListener.java @@ -37,7 +37,9 @@ public class MouseOverListener extends JSFunctionExec implements int i = -1; - public void mouseOver(SequenceI seq, int index, VamsasSource source) + @Override + public void mouseOverSequence(SequenceI seq, int index, + VamsasSource source) { if (seq != last || i != index) { diff --git a/src/jalview/javascript/MouseOverStructureListener.java b/src/jalview/javascript/MouseOverStructureListener.java index 465a672..8a32c5b 100644 --- a/src/jalview/javascript/MouseOverStructureListener.java +++ b/src/jalview/javascript/MouseOverStructureListener.java @@ -20,9 +20,6 @@ */ package jalview.javascript; -import java.awt.Color; -import java.util.ArrayList; - import jalview.api.AlignmentViewPanel; import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; @@ -30,11 +27,15 @@ import jalview.appletgui.AlignFrame; import jalview.bin.JalviewLite; import jalview.datamodel.SequenceI; import jalview.ext.jmol.JmolCommands; +import jalview.structure.AtomSpec; import jalview.structure.StructureListener; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; +import java.util.ArrayList; +import java.util.List; + /** * Propagate events involving PDB structures associated with sequences to a * javascript function. Generally, the javascript handler is called with a @@ -133,7 +134,6 @@ public class MouseOverStructureListener extends JSFunctionExec implements return modelSet; } - @Override public void mouseOverStructure(int atomIndex, String strInfo) { @@ -144,24 +144,22 @@ public class MouseOverStructureListener extends JSFunctionExec implements } @Override - public void highlightAtom(int atomIndex, int pdbResNum, String chain, - String pdbId) + public void highlightAtoms(List atoms) { - String[] st = new String[0]; - try - { - executeJavascriptFunction(_listenerfn, st = new String[] - { "mouseover", "" + pdbId, "" + chain, "" + (pdbResNum), - "" + atomIndex }); - } catch (Exception ex) + for (AtomSpec atom : atoms) { - System.err.println("Couldn't execute callback with " + _listenerfn - + " using args { " + st[0] + ", " + st[1] + ", " + st[2] - + "," + st[3] + "\n"); - ex.printStackTrace(); - + try + { + executeJavascriptFunction(_listenerfn, new String[] + { "mouseover", "" + atom.getPdbId(), "" + atom.getChain(), + "" + (atom.getPdbResNum()), "" + atom.getAtomIndex() }); + } catch (Exception ex) + { + System.err.println("Couldn't execute callback with " + _listenerfn + + " for atomSpec: " + atom); + ex.printStackTrace(); + } } - } @Override @@ -283,13 +281,6 @@ public class MouseOverStructureListener extends JSFunctionExec implements } @Override - public Color getColour(int atomIndex, int pdbResNum, String chain, - String pdbId) - { - return null; - } - - @Override public AlignFrame getAlignFrame() { // associated with all alignframes, always. diff --git a/src/jalview/structure/AtomSpec.java b/src/jalview/structure/AtomSpec.java new file mode 100644 index 0000000..271c893 --- /dev/null +++ b/src/jalview/structure/AtomSpec.java @@ -0,0 +1,61 @@ +package jalview.structure; + +/** + * Java bean representing an atom in a PDB (or similar) structure model. + * + * @author gmcarstairs + * + */ +public class AtomSpec +{ + private String pdbId; + + private String chain; + + private int pdbResNum; + + private int atomIndex; + + /** + * Constructor + * + * @param id + * @param chain + * @param resNo + * @param atomNo + */ + public AtomSpec(String id, String chain, int resNo, int atomNo) + { + this.pdbId = id; + this.chain = chain; + this.pdbResNum = resNo; + this.atomIndex = atomNo; + } + + public String getPdbId() + { + return pdbId; + } + + public String getChain() + { + return chain; + } + + public int getPdbResNum() + { + return pdbResNum; + } + + public int getAtomIndex() + { + return atomIndex; + } + + @Override + public String toString() + { + return "pdbId: " + pdbId + ", chain: " + chain + ", res: " + pdbResNum + + ", atom: " + atomIndex; + } +} diff --git a/src/jalview/structure/CommandListener.java b/src/jalview/structure/CommandListener.java new file mode 100644 index 0000000..66f39fe --- /dev/null +++ b/src/jalview/structure/CommandListener.java @@ -0,0 +1,33 @@ +package jalview.structure; + +import jalview.commands.CommandI; + +/** + * Defines a listener for commands performed on another alignment. This is to + * support linked editing of two alternative representations of an alignment (in + * particular, cDNA and protein). + * + * @author gmcarstairs + * + */ +public interface CommandListener +{ + /** + * The listener may attempt to perform the specified command; the region acted + * on is determined by a callback to the StructureSelectionManager (which + * holds mappings between alignments). + * + * @param command + * @param undo + * @param ssm + */ + public void mirrorCommand(CommandI command, boolean undo, + StructureSelectionManager ssm); + + /** + * Temporary workaround to make check for source == listener work. + * + * @return + */ + public VamsasSource getVamsasSource(); +} diff --git a/src/jalview/structure/SequenceListener.java b/src/jalview/structure/SequenceListener.java index 83a9fd0..f2a40a7 100644 --- a/src/jalview/structure/SequenceListener.java +++ b/src/jalview/structure/SequenceListener.java @@ -20,13 +20,13 @@ */ package jalview.structure; -import jalview.datamodel.*; +import jalview.datamodel.SearchResults; + public interface SequenceListener { - public void mouseOverSequence(SequenceI sequence, int index, int pos); - - public void highlightSequence(jalview.datamodel.SearchResults results); + public void highlightSequence(SearchResults results); - public void updateColours(SequenceI sequence, int index); + // Required so we can check for (and skip if) target == source + public VamsasSource getVamsasSource(); } diff --git a/src/jalview/structure/StructureListener.java b/src/jalview/structure/StructureListener.java index 8ac002b..032c40b 100644 --- a/src/jalview/structure/StructureListener.java +++ b/src/jalview/structure/StructureListener.java @@ -20,40 +20,27 @@ */ package jalview.structure; +import java.util.List; + public interface StructureListener { /** - * - * @return list of structure files (unique IDs/filenames) that this listener - * handles messages for, or null if generic listener (only used by - * removeListener method) + * Returns a list of structure files (unique IDs/filenames) that this listener + * handles messages for, or null if generic listener (only used by + * removeListener method) */ public String[] getPdbFile(); /** - * NOT A LISTENER METHOD! called by structure viewer when the given - * atom/structure has been moused over. Typically, implementors call - * StructureSelectionManager.mouseOverStructure - * - * @param atomIndex - * @param strInfo - */ - public void mouseOverStructure(int atomIndex, String strInfo); - - /** - * called by StructureSelectionManager to inform viewer to highlight given - * atomspec + * Called by StructureSelectionManager to inform viewer to highlight given + * atom positions * - * @param atomIndex - * @param pdbResNum - * @param chain - * @param pdbId + * @param atoms */ - public void highlightAtom(int atomIndex, int pdbResNum, String chain, - String pdbId); + public void highlightAtoms(List atoms); /** - * called by StructureSelectionManager when the colours of a sequence + * Called by StructureSelectionManager when the colours of a sequence * associated with a structure have changed. * * @param source @@ -62,19 +49,7 @@ public interface StructureListener public void updateColours(Object source); /** - * called by Jalview to get the colour for the given atomspec - * - * @param atomIndex - * @param pdbResNum - * @param chain - * @param pdbId - * @return - */ - public java.awt.Color getColour(int atomIndex, int pdbResNum, - String chain, String pdbId); - - /** - * called by structureSelectionManager to instruct implementor to release any + * Called by structureSelectionManager to instruct implementor to release any * direct references it may hold to the given object (typically, these are * Jalview alignment panels). * diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index af00798..3fead00 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -22,19 +22,29 @@ package jalview.structure; import jalview.analysis.AlignSeq; import jalview.api.StructureSelectionManagerProvider; +import jalview.commands.CommandI; +import jalview.commands.EditCommand; +import jalview.commands.EditCommand.Action; +import jalview.commands.EditCommand.Edit; import jalview.datamodel.AlignedCodonFrame; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.Annotation; import jalview.datamodel.PDBEntry; import jalview.datamodel.SearchResults; +import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; import jalview.io.AppletFormatAdapter; import jalview.util.MessageManager; +import jalview.util.StringUtils; import java.io.PrintStream; +import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Vector; import MCview.Atom; @@ -49,6 +59,12 @@ public class StructureSelectionManager private boolean processSecondaryStructure = false, secStructServices = false, addTempFacAnnot = false; + List seqmappings = null; + + private int[] seqmappingrefs = null; // refcount for seqmappings elements + + private List commandListeners = new ArrayList(); + /** * @return true if will try to use external services for processing secondary * structure @@ -597,17 +613,13 @@ public class StructureSelectionManager // construct highlighted sequence list if (seqmappings != null) { - - Enumeration e = seqmappings.elements(); - while (e.hasMoreElements()) - + for (AlignedCodonFrame acf : seqmappings) { - ((AlignedCodonFrame) e.nextElement()).markMappedRegion( - mappings[j].sequence, indexpos, results); + acf.markMappedRegion(mappings[j].sequence, indexpos, + results); } } } - } } } @@ -626,13 +638,11 @@ public class StructureSelectionManager } } - Vector seqmappings = null; // should be a simpler list of mapped seuqence - /** * highlight regions associated with a position (indexpos) in seq * * @param seq - * the sequeence that the mouse over occured on + * the sequence that the mouse over occurred on * @param indexpos * the absolute position being mouseovered in seq (0 to seq.length()) * @param index @@ -648,87 +658,41 @@ public class StructureSelectionManager { index = seq.findPosition(indexpos); } - StructureListener sl; - int atomNo = 0; for (int i = 0; i < listeners.size(); i++) { Object listener = listeners.elementAt(i); if (listener == source) { + // TODO listener (e.g. SeqPanel) is never == source (AlignViewport) + // Temporary fudge with SequenceListener.getVamsasSource() continue; } if (listener instanceof StructureListener) { - sl = (StructureListener) listener; - if (mappings == null) - { - continue; - } - for (int j = 0; j < mappings.length; j++) - { - if (mappings[j].sequence == seq - || mappings[j].sequence == seq.getDatasetSequence()) - { - atomNo = mappings[j].getAtomNum(index); - - if (atomNo > 0) - { - sl.highlightAtom(atomNo, mappings[j].getPDBResNum(index), - mappings[j].pdbchain, mappings[j].pdbfile); - } - } - } + highlightStructure((StructureListener) listener, seq, index); } else { - if (relaySeqMappings && hasSequenceListeners - && listener instanceof SequenceListener) + if (listener instanceof SequenceListener) { - // DEBUG - // System.err.println("relay Seq " + seq.getDisplayId(false) + " " + - // index); - - if (results == null) + final SequenceListener seqListener = (SequenceListener) listener; + if (hasSequenceListeners + && seqListener.getVamsasSource() != source) { - results = new SearchResults(); - if (index >= seq.getStart() && index <= seq.getEnd()) + if (relaySeqMappings) { - // construct highlighted sequence list - - if (seqmappings != null) - { - Enumeration e = seqmappings.elements(); - while (e.hasMoreElements()) - - { - ((AlignedCodonFrame) e.nextElement()).markMappedRegion( - seq, index, results); - } - } - // hasSequenceListeners = results.getSize() > 0; - if (handlingVamsasMo) + if (results == null) { - // maybe have to resolve seq to a dataset seqeunce... - // add in additional direct sequence and/or dataset sequence - // highlighting - results.addResult(seq, index, index); + results = buildSearchResults(seq, index); } + seqListener.highlightSequence(results); } } - if (hasSequenceListeners) - { - ((SequenceListener) listener).highlightSequence(results); - } } else if (listener instanceof VamsasListener && !handlingVamsasMo) { - // DEBUG - // System.err.println("Vamsas from Seq " + seq.getDisplayId(false) + " - // " + - // index); - // pass the mouse over and absolute position onto the - // VamsasListener(s) - ((VamsasListener) listener).mouseOver(seq, indexpos, source); + ((VamsasListener) listener).mouseOverSequence(seq, indexpos, + source); } else if (listener instanceof SecondaryStructureListener) { @@ -740,6 +704,73 @@ public class StructureSelectionManager } /** + * Returns a SearchResults object describing the mapped region corresponding + * to the specified sequence position. + * + * @param seq + * @param index + * @return + */ + protected SearchResults buildSearchResults(SequenceI seq, int index) + { + SearchResults results; + results = new SearchResults(); + if (index >= seq.getStart() && index <= seq.getEnd()) + { + if (seqmappings != null) + { + for (AlignedCodonFrame acf : seqmappings) + { + acf.markMappedRegion(seq, index, results); + } + } + // hasSequenceListeners = results.getSize() > 0; + if (handlingVamsasMo) + { + // maybe have to resolve seq to a dataset sequence... + // add in additional direct sequence and/or dataset sequence + // highlighting + results.addResult(seq, index, index); + } + } + return results; + } + + /** + * Send suitable messages to a StructureListener to highlight atoms + * corresponding to the given sequence position. + * + * @param sl + * @param seq + * @param index + */ + protected void highlightStructure(StructureListener sl, SequenceI seq, + int index) + { + int atomNo; + if (mappings != null) + { + List atoms = new ArrayList(); + for (int j = 0; j < mappings.length; j++) + { + if (mappings[j].sequence == seq + || mappings[j].sequence == seq.getDatasetSequence()) + { + atomNo = mappings[j].getAtomNum(index); + + if (atomNo > 0) + { + atoms.add(new AtomSpec(mappings[j].pdbfile, + mappings[j].pdbchain, mappings[j].getPDBResNum(index), + atomNo)); + } + } + } + sl.highlightAtoms(atoms); + } + } + + /** * true if a mouse over event from an external (ie Vamsas) source is being * handled */ @@ -859,8 +890,6 @@ public class StructureSelectionManager return sb.toString(); } - private int[] seqmappingrefs = null; // refcount for seqmappings elements - private synchronized void modifySeqMappingList(boolean add, AlignedCodonFrame[] codonFrames) { @@ -870,7 +899,7 @@ public class StructureSelectionManager } if (seqmappings == null) { - seqmappings = new Vector(); + seqmappings = new ArrayList(); } if (codonFrames != null && codonFrames.length > 0) { @@ -904,7 +933,7 @@ public class StructureSelectionManager { if (add) { - seqmappings.addElement(codonFrames[cf]); + seqmappings.add(codonFrames[cf]); int[] nsr = new int[(seqmappingrefs == null) ? 1 : seqmappingrefs.length + 1]; @@ -1055,4 +1084,176 @@ public class StructureSelectionManager } } + public void addCommandListener(CommandListener cl) + { + if (!commandListeners.contains(cl)) + { + commandListeners.add(cl); + } + } + + public boolean hasCommandListener(CommandListener cl) + { + return this.commandListeners.contains(cl); + } + + public boolean removeEditListener(CommandListener l) + { + return commandListeners.remove(l); + } + + /** + * Forward a command to any command listeners (except for the command's + * source). + * + * @param command + * the command to be broadcast (in its form after being performed) + * @param undo + * if true, the command was being 'undone' + * @param source + */ + public void commandPerformed(CommandI command, boolean undo, + VamsasSource source) + { + for (CommandListener listener : commandListeners) + { + if (listener.getVamsasSource() != source) + { + listener.mirrorCommand(command, undo, this); + } + } + } + + /** + * Returns a new EditCommand representing the given command as mapped to the + * given sequences. If there is no mapping, returns an empty EditCommand. + * + * @param command + * @param undo + * @param targetSeqs + * @param gapChar + * @return + */ + public EditCommand mapEditCommand(EditCommand command, boolean undo, + final List targetSeqs, char gapChar) + { + /* + * Cache a copy of the target sequences so we can mimic successive edits on + * them. This lets us compute mappings for all edits in the set. + */ + Map targetCopies = new HashMap(); + for (SequenceI seq : targetSeqs) + { + SequenceI ds = seq.getDatasetSequence(); + if (ds != null) + { + final Sequence copy = new Sequence("", new String(seq.getSequence())); + copy.setDatasetSequence(ds); + targetCopies.put(ds, copy); + } + } + + /* + * Compute 'source' sequences as they were before applying edits: + */ + Map originalSequences = command.priorState(undo); + + EditCommand result = new EditCommand(); + Iterator edits = command.getEditIterator(!undo); + while (edits.hasNext()) + { + Edit edit = edits.next(); + Action action = edit.getAction(); + + /* + * Invert sense of action if an Undo. + */ + if (undo) + { + action = action == Action.INSERT_GAP ? Action.DELETE_GAP + : (action == Action.DELETE_GAP ? Action.INSERT_GAP : action); + } + final int count = edit.getNumber(); + final int editPos = edit.getPosition(); + for (SequenceI seq : edit.getSequences()) + { + /* + * Get residue position at (or to right of) edit location. Note we use + * our 'copy' of the sequence before editing for this. + */ + SequenceI ds = seq.getDatasetSequence(); + if (ds == null) + { + continue; + } + final SequenceI actedOn = originalSequences.get(ds); + final int seqpos = actedOn.findPosition(editPos); + + /* + * Determine all mappings from this position to mapped sequences. + */ + SearchResults sr = buildSearchResults(seq, seqpos); + + if (!sr.isEmpty()) + { + for (SequenceI targetSeq : targetSeqs) + { + ds = targetSeq.getDatasetSequence(); + if (ds == null) + { + continue; + } + SequenceI copyTarget = targetCopies.get(ds); + final int[] match = sr.getResults(copyTarget, 0, + copyTarget.getLength()); + if (match != null) + { + final int ratio = 3; // TODO: compute this - how? + final int mappedCount = count * ratio; + + /* + * Shift Delete start position left, as it acts on positions to + * its right. + */ + int mappedEditPos = action == Action.DELETE_GAP ? match[0] + - mappedCount : match[0]; + Edit e = new EditCommand().new Edit(action, new SequenceI[] + { targetSeq }, mappedEditPos, mappedCount, gapChar); + result.addEdit(e); + + /* + * and 'apply' the edit to our copy of its target sequence + */ + if (action == Action.INSERT_GAP) + { + copyTarget.setSequence(new String(StringUtils.insertCharAt( + copyTarget.getSequence(), mappedEditPos, + mappedCount, + gapChar))); + } + else if (action == Action.DELETE_GAP) + { + copyTarget.setSequence(new String(StringUtils.deleteChars( + copyTarget.getSequence(), mappedEditPos, + mappedEditPos + mappedCount))); + } + } + } + } + /* + * and 'apply' the edit to our copy of its source sequence + */ + if (action == Action.INSERT_GAP) { + actedOn.setSequence(new String(StringUtils.insertCharAt( + actedOn.getSequence(), editPos, count, gapChar))); + } + else if (action == Action.DELETE_GAP) + { + actedOn.setSequence(new String(StringUtils.deleteChars( + actedOn.getSequence(), editPos, editPos + count))); + } + } + } + return result; + } } diff --git a/src/jalview/structure/VamsasListener.java b/src/jalview/structure/VamsasListener.java index 26ebdc1..1c49252 100644 --- a/src/jalview/structure/VamsasListener.java +++ b/src/jalview/structure/VamsasListener.java @@ -36,5 +36,6 @@ import jalview.datamodel.SequenceI; */ public interface VamsasListener { - public void mouseOver(SequenceI seq, int index, VamsasSource source); + public void mouseOverSequence(SequenceI seq, int index, + VamsasSource source); } diff --git a/src/jalview/util/Comparison.java b/src/jalview/util/Comparison.java index 8931b23..259a5d8 100644 --- a/src/jalview/util/Comparison.java +++ b/src/jalview/util/Comparison.java @@ -20,17 +20,17 @@ */ package jalview.util; -import jalview.datamodel.*; +import jalview.datamodel.SequenceI; /** - * DOCUMENT ME! - * - * @author $author$ - * @version $Revision$ + * Assorted methods for analysing or comparing sequences. */ public class Comparison { - /** DOCUMENT ME!! */ + private static final int EIGHTY_FIVE = 85; + + private static final int TO_UPPER_CASE = 'a' - 'A'; + public static final String GapChars = " .-"; /** @@ -69,12 +69,12 @@ public class Comparison int ilen = si.length() - 1; int jlen = sj.length() - 1; - while (jalview.util.Comparison.isGap(si.charAt(start + ilen))) + while (Comparison.isGap(si.charAt(start + ilen))) { ilen--; } - while (jalview.util.Comparison.isGap(sj.charAt(start + jlen))) + while (Comparison.isGap(sj.charAt(start + jlen))) { jlen--; } @@ -225,47 +225,60 @@ public class Comparison } /** - * DOCUMENT ME! + * Answers true if the supplied character is a recognised gap character, else + * false. Currently hard-coded to recognise '-', '-' or ' ' (hyphen / dot / + * space). * * @param c - * DOCUMENT ME! * - * @return DOCUMENT ME! + * @return */ public static final boolean isGap(char c) { return (c == '-' || c == '.' || c == ' ') ? true : false; } + /** + * Answers true if more than 85% of the sequence residues (ignoring gaps) are + * A, G, C, T or U, else false. This is just a heuristic guess and may give a + * wrong answer (as AGCT are also animo acid codes). + * + * @param seqs + * @return + */ public static final boolean isNucleotide(SequenceI[] seqs) { - int i = 0, iSize = seqs.length, j, jSize; - float nt = 0, aa = 0; - char c; - while (i < iSize) + if (seqs == null) + { + return false; + } + int ntCount = 0; + int aaCount = 0; + for (SequenceI seq : seqs) { - jSize = seqs[i].getLength(); - for (j = 0; j < jSize; j++) + for (char c : seq.getSequence()) { - c = seqs[i].getCharAt(j); if ('a' <= c && c <= 'z') { - c -= ('a' - 'A'); + c -= TO_UPPER_CASE; } if (c == 'A' || c == 'G' || c == 'C' || c == 'T' || c == 'U') { - nt++; + ntCount++; } - else if (!jalview.util.Comparison.isGap(seqs[i].getCharAt(j))) + else if (!Comparison.isGap(c)) { - aa++; + aaCount++; } } - i++; } - if ((nt / (nt + aa)) > 0.85f) + /* + * Check for nucleotide count > 85% of total count (in a form that evades + * int / float conversion or divide by zero). + */ + if (ntCount * 100 > EIGHTY_FIVE * (ntCount + aaCount)) { return true; } diff --git a/src/jalview/util/ReverseListIterator.java b/src/jalview/util/ReverseListIterator.java new file mode 100644 index 0000000..69a7345 --- /dev/null +++ b/src/jalview/util/ReverseListIterator.java @@ -0,0 +1,42 @@ +package jalview.util; + +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * An iterator that traverses a list backwards. + * + * @author gmcarstairs (and checked against + * org.codehaus.groovey.runtime.ReverseListIterator) + * + * @param + */ +public class ReverseListIterator implements Iterator +{ + + private ListIterator iterator; + + public ReverseListIterator(List stuff) + { + this.iterator = stuff.listIterator(stuff.size()); + } + @Override + public boolean hasNext() + { + return iterator.hasPrevious(); + } + + @Override + public E next() + { + return iterator.previous(); + } + + @Override + public void remove() + { + iterator.remove(); + } + +} diff --git a/src/jalview/util/StringUtils.java b/src/jalview/util/StringUtils.java new file mode 100644 index 0000000..c1e050f --- /dev/null +++ b/src/jalview/util/StringUtils.java @@ -0,0 +1,82 @@ +package jalview.util; + +public class StringUtils +{ + + /** + * Returns a new character array, after inserting characters into the given + * character array. + * + * @param in + * the character array to insert into + * @param position + * the 0-based position for insertion + * @param count + * the number of characters to insert + * @param ch + * the character to insert + */ + public static final char[] insertCharAt(char[] in, int position, + int count, + char ch) + { + char[] tmp = new char[in.length + count]; + + if (position >= in.length) + { + System.arraycopy(in, 0, tmp, 0, in.length); + position = in.length; + } + else + { + System.arraycopy(in, 0, tmp, 0, position); + } + + int index = position; + while (count > 0) + { + tmp[index++] = ch; + count--; + } + + if (position < in.length) + { + System.arraycopy(in, position, tmp, index, + in.length - position); + } + + return tmp; + } + + /** + * Delete + * + * @param in + * @param from + * @param to + * @return + */ + public static final char[] deleteChars(char[] in, int from, int to) + { + if (from >= in.length) + { + return in; + } + + char[] tmp; + + if (to >= in.length) + { + tmp = new char[from]; + System.arraycopy(in, 0, tmp, 0, from); + to = in.length; + } + else + { + tmp = new char[in.length - to + from]; + System.arraycopy(in, 0, tmp, 0, from); + System.arraycopy(in, to, tmp, from, in.length - to); + } + return tmp; + } +} diff --git a/test/jalview/commands/EditCommandTest.java b/test/jalview/commands/EditCommandTest.java index fc821b9..84671ae 100644 --- a/test/jalview/commands/EditCommandTest.java +++ b/test/jalview/commands/EditCommandTest.java @@ -1,6 +1,7 @@ package jalview.commands; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import jalview.commands.EditCommand.Action; import jalview.commands.EditCommand.Edit; import jalview.datamodel.Alignment; @@ -8,6 +9,8 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; +import java.util.Map; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -33,9 +36,13 @@ public class EditCommandTest testee = new EditCommand(); seqs = new SequenceI[4]; seqs[0] = new Sequence("seq0", "abcdefghjk"); + seqs[0].setDatasetSequence(new Sequence("seq0ds", "abcdefghjk")); seqs[1] = new Sequence("seq1", "fghjklmnopq"); + seqs[1].setDatasetSequence(new Sequence("seq1ds", "fghjklmnopq")); seqs[2] = new Sequence("seq2", "qrstuvwxyz"); + seqs[2].setDatasetSequence(new Sequence("seq2ds", "qrstuvwxyz")); seqs[3] = new Sequence("seq3", "1234567890"); + seqs[3].setDatasetSequence(new Sequence("seq3ds", "1234567890")); al = new Alignment(seqs); al.setGapCharacter('?'); } @@ -227,6 +234,374 @@ public class EditCommandTest assertEquals("qrstuvwxyz", seqs[2].getSequenceAsString()); assertEquals("1234567890", seqs[3].getSequenceAsString()); seqs[1] = new Sequence("seq1", "fghjZXYnopq"); + } + + /** + * Test that the addEdit command correctly merges insert gap commands when + * possible. + */ + @Test + public void testAddEdit_multipleInsertGap() + { + /* + * 3 insert gap in a row (aka mouse drag right): + */ + Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] + { seqs[0] }, 1, 1, al); + testee.addEdit(e); + SequenceI edited = new Sequence("seq0", "a?bcdefghjk"); + edited.setDatasetSequence(seqs[0].getDatasetSequence()); + e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] + { edited }, 2, 1, al); + testee.addEdit(e); + edited = new Sequence("seq0", "a??bcdefghjk"); + edited.setDatasetSequence(seqs[0].getDatasetSequence()); + e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] + { edited }, 3, 1, al); + testee.addEdit(e); + assertEquals(1, testee.getSize()); + assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction()); + assertEquals(1, testee.getEdit(0).getPosition()); + assertEquals(3, testee.getEdit(0).getNumber()); + + /* + * Add a non-contiguous edit - should not be merged. + */ + e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] + { edited }, 5, 2, al); + testee.addEdit(e); + assertEquals(2, testee.getSize()); + assertEquals(5, testee.getEdit(1).getPosition()); + assertEquals(2, testee.getEdit(1).getNumber()); + + /* + * Add a Delete after the Insert - should not be merged. + */ + e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] + { edited }, 6, 2, al); + testee.addEdit(e); + assertEquals(3, testee.getSize()); + assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction()); + assertEquals(6, testee.getEdit(2).getPosition()); + assertEquals(2, testee.getEdit(2).getNumber()); + } + + /** + * Test that the addEdit command correctly merges delete gap commands when + * possible. + */ + @Test + public void testAddEdit_multipleDeleteGap() + { + /* + * 3 delete gap in a row (aka mouse drag left): + */ + seqs[0].setSequence("a???bcdefghjk"); + Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] + { seqs[0] }, 4, 1, al); + testee.addEdit(e); + assertEquals(1, testee.getSize()); + + SequenceI edited = new Sequence("seq0", "a??bcdefghjk"); + edited.setDatasetSequence(seqs[0].getDatasetSequence()); + e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] + { edited }, 3, 1, al); + testee.addEdit(e); + assertEquals(1, testee.getSize()); + + edited = new Sequence("seq0", "a?bcdefghjk"); + edited.setDatasetSequence(seqs[0].getDatasetSequence()); + e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] + { edited }, 2, 1, al); + testee.addEdit(e); + assertEquals(1, testee.getSize()); + assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction()); + assertEquals(2, testee.getEdit(0).getPosition()); + assertEquals(3, testee.getEdit(0).getNumber()); + + /* + * Add a non-contiguous edit - should not be merged. + */ + e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] + { edited }, 2, 1, al); + testee.addEdit(e); + assertEquals(2, testee.getSize()); + assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction()); + assertEquals(2, testee.getEdit(1).getPosition()); + assertEquals(1, testee.getEdit(1).getNumber()); + + /* + * Add an Insert after the Delete - should not be merged. + */ + e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] + { edited }, 1, 1, al); + testee.addEdit(e); + assertEquals(3, testee.getSize()); + assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction()); + assertEquals(1, testee.getEdit(2).getPosition()); + assertEquals(1, testee.getEdit(2).getNumber()); + } + + /** + * Test that the addEdit command correctly handles 'remove gaps' edits for the + * case when they appear contiguous but are acting on different sequences. + * They should not be merged. + */ + @Test + public void testAddEdit_removeAllGaps() + { + seqs[0].setSequence("a???bcdefghjk"); + Edit e = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] + { seqs[0] }, 4, 1, al); + testee.addEdit(e); + + seqs[1].setSequence("f??ghjklmnopq"); + Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[] + { seqs[1] }, 3, 1, al); + testee.addEdit(e2); + assertEquals(2, testee.getSize()); + assertSame(e, testee.getEdit(0)); + assertSame(e2, testee.getEdit(1)); + } + + /** + * Test that the addEdit command correctly merges insert gap commands acting + * on a multi-sequence selection. + */ + @Test + public void testAddEdit_groupInsertGaps() + { + /* + * 2 insert gap in a row (aka mouse drag right), on two sequences: + */ + Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] + { seqs[0], seqs[1] }, 1, 1, al); + testee.addEdit(e); + SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk"); + seq1edited.setDatasetSequence(seqs[0].getDatasetSequence()); + SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq"); + seq2edited.setDatasetSequence(seqs[1].getDatasetSequence()); + e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] + { seq1edited, seq2edited }, 2, 1, al); + testee.addEdit(e); + + assertEquals(1, testee.getSize()); + assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction()); + assertEquals(1, testee.getEdit(0).getPosition()); + assertEquals(2, testee.getEdit(0).getNumber()); + assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0) + .getSequences()[0].getDatasetSequence()); + assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0) + .getSequences()[1].getDatasetSequence()); + } + + /** + * Test for 'undoing' a series of gap insertions. + *
    + *
  • Start: ABCDEF insert 2 at pos 1
  • + *
  • next: A--BCDEF insert 1 at pos 4
  • + *
  • next: A--B-CDEF insert 2 at pos 0
  • + *
  • last: --A--B-CDEF
  • + *
+ */ + @Test + public void testUnwindCommand_multipleInserts() + { + EditCommand command = new EditCommand(); + SequenceI seq = new Sequence("", "--A--B-CDEF"); + SequenceI ds = new Sequence("", "ABCDEF"); + seq.setDatasetSequence(ds); + SequenceI[] seqs = new SequenceI[] + { seq }; + Edit e = command.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-'); + command.addEdit(e); + e = command.new Edit(Action.INSERT_GAP, seqs, 4, 1, '-'); + command.addEdit(e); + e = command.new Edit(Action.INSERT_GAP, seqs, 0, 2, '-'); + command.addEdit(e); + + Map unwound = command.priorState(false); + assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString()); + } + + /** + * Test for 'undoing' a series of gap deletions. + *
    + *
  • Start: A-B-C delete 1 at pos 1
  • + *
  • Next: AB-C delete 1 at pos 2
  • + *
  • End: ABC
  • + *
+ */ + @Test + public void testUnwindCommand_removeAllGaps() + { + EditCommand command = new EditCommand(); + SequenceI seq = new Sequence("", "ABC"); + SequenceI ds = new Sequence("", "ABC"); + seq.setDatasetSequence(ds); + SequenceI[] seqs = new SequenceI[] + { seq }; + Edit e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'); + command.addEdit(e); + e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-'); + command.addEdit(e); + + Map unwound = command.priorState(false); + assertEquals("A-B-C", unwound.get(ds).getSequenceAsString()); + } + + /** + * Test for 'undoing' a single delete edit. + */ + @Test + public void testUnwindCommand_singleDelete() + { + EditCommand command = new EditCommand(); + SequenceI seq = new Sequence("", "ABCDEF"); + SequenceI ds = new Sequence("", "ABCDEF"); + seq.setDatasetSequence(ds); + SequenceI[] seqs = new SequenceI[] + { seq }; + Edit e = command.new Edit(Action.DELETE_GAP, seqs, 2, 2, '-'); + command.addEdit(e); + + Map unwound = command.priorState(false); + assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString()); + } + + /** + * Test method that 'restores' edit commands to hold the sequence as it was + * before the edit was applied. + */ + @Test + public void testUnwindCommand_singleInsert() + { + EditCommand command = new EditCommand(); + SequenceI seq = new Sequence("", "AB---CDEF"); + SequenceI ds = new Sequence("", "ABCDEF"); + seq.setDatasetSequence(ds); + SequenceI[] seqs = new SequenceI[] + { seq }; + Edit e = command.new Edit(Action.INSERT_GAP, seqs, 2, 3, '-'); + command.addEdit(e); + + Map unwound = command.priorState(false); + assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString()); + } + + /** + * Test that mimics 'remove all gaps' action. This generates delete gap edits + * for contiguous gaps in each sequence separately. + */ + @Test + public void testUnwindCommand_removeGapsMultipleSeqs() + { + EditCommand command = new EditCommand(); + String original1 = "--ABC-DEF"; + String original2 = "FG-HI--J"; + String original3 = "M-NOPQ"; + + /* + * Two edits for the first sequence + */ + SequenceI seq = new Sequence("", "ABC-DEF"); + SequenceI ds1 = new Sequence("", "ABCDEF"); + seq.setDatasetSequence(ds1); + SequenceI[] seqs = new SequenceI[] + { seq }; + Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 2, '-'); + command.addEdit(e); + seq = new Sequence("", "ABCDEF"); + seq.setDatasetSequence(ds1); + seqs = new SequenceI[] + { seq }; + e = command.new Edit(Action.DELETE_GAP, seqs, 3, 1, '-'); + command.addEdit(e); + + /* + * Two edits for the second sequence + */ + seq = new Sequence("", "FGHI--J"); + SequenceI ds2 = new Sequence("", "FGHIJ"); + seq.setDatasetSequence(ds2); + seqs = new SequenceI[] + { seq }; + e = command.new Edit(Action.DELETE_GAP, seqs, 2, 1, '-'); + command.addEdit(e); + seq = new Sequence("", "FGHIJ"); + seq.setDatasetSequence(ds2); + seqs = new SequenceI[] + { seq }; + e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-'); + command.addEdit(e); + + /* + * One edit for the third sequence. + */ + seq = new Sequence("", "MNOPQ"); + SequenceI ds3 = new Sequence("", "MNOPQ"); + seq.setDatasetSequence(ds3); + seqs = new SequenceI[] + { seq }; + e = command.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'); + command.addEdit(e); + + Map unwound = command.priorState(false); + assertEquals(original1, unwound.get(ds1).getSequenceAsString()); + assertEquals(original2, unwound.get(ds2).getSequenceAsString()); + assertEquals(original3, unwound.get(ds3).getSequenceAsString()); + } + + /** + * Test that mimics 'remove all gapped columns' action. This generates a + * series Delete Gap edits that each act on all sequences that share a gapped + * column region. + */ + @Test + public void testUnwindCommand_removeGappedCols() + { + EditCommand command = new EditCommand(); + String original1 = "--ABC--DEF"; + String original2 = "-G-HI--J"; + String original3 = "-M-NO--PQ"; + + /* + * First edit deletes the first column. + */ + SequenceI seq1 = new Sequence("", "-ABC--DEF"); + SequenceI ds1 = new Sequence("", "ABCDEF"); + seq1.setDatasetSequence(ds1); + SequenceI seq2 = new Sequence("", "G-HI--J"); + SequenceI ds2 = new Sequence("", "GHIJ"); + seq2.setDatasetSequence(ds2); + SequenceI seq3 = new Sequence("", "M-NO--PQ"); + SequenceI ds3 = new Sequence("", "MNOPQ"); + seq3.setDatasetSequence(ds3); + SequenceI[] seqs = new SequenceI[] + { seq1, seq2, seq3 }; + Edit e = command.new Edit(Action.DELETE_GAP, seqs, 0, 1, '-'); + command.addEdit(e); + + /* + * Second edit deletes what is now columns 4 and 5. + */ + seq1 = new Sequence("", "-ABCDEF"); + seq1.setDatasetSequence(ds1); + seq2 = new Sequence("", "G-HIJ"); + seq2.setDatasetSequence(ds2); + seq3 = new Sequence("", "M-NOPQ"); + seq3.setDatasetSequence(ds3); + seqs = new SequenceI[] + { seq1, seq2, seq3 }; + e = command.new Edit(Action.DELETE_GAP, seqs, 4, 2, '-'); + command.addEdit(e); + Map unwound = command.priorState(false); + assertEquals(original1, unwound.get(ds1).getSequenceAsString()); + assertEquals(original2, unwound.get(ds2).getSequenceAsString()); + assertEquals(original3, unwound.get(ds3).getSequenceAsString()); + assertEquals(ds1, unwound.get(ds1).getDatasetSequence()); + assertEquals(ds2, unwound.get(ds2).getDatasetSequence()); + assertEquals(ds3, unwound.get(ds3).getDatasetSequence()); } } diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index 3f91710..2a23104 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -120,4 +120,86 @@ public class SequenceTest assertSame(annotation2, anns[1]); } + + @Test + public void testGetStartGetEnd() + { + SequenceI seq = new Sequence("test", "ABCDEF"); + assertEquals(1, seq.getStart()); + assertEquals(6, seq.getEnd()); + + seq = new Sequence("test", "--AB-C-DEF--"); + assertEquals(1, seq.getStart()); + assertEquals(6, seq.getEnd()); + + seq = new Sequence("test", "----"); + assertEquals(1, seq.getStart()); + assertEquals(0, seq.getEnd()); // ?? + } + + @Test + public void testFindPosition() + { + SequenceI seq = new Sequence("test", "ABCDEF"); + assertEquals(1, seq.findPosition(0)); + assertEquals(6, seq.findPosition(5)); + // assertEquals(-1, seq.findPosition(6)); // fails + + seq = new Sequence("test", "AB-C-D--"); + assertEquals(1, seq.findPosition(0)); + assertEquals(2, seq.findPosition(1)); + // gap position 'finds' residue to the right (not the left as per javadoc) + assertEquals(3, seq.findPosition(2)); + assertEquals(3, seq.findPosition(3)); + assertEquals(4, seq.findPosition(4)); + assertEquals(4, seq.findPosition(5)); + // returns 1 more than sequence length if off the end ?!? + assertEquals(5, seq.findPosition(6)); + assertEquals(5, seq.findPosition(7)); + + seq = new Sequence("test", "--AB-C-DEF--"); + assertEquals(1, seq.findPosition(0)); + assertEquals(1, seq.findPosition(1)); + assertEquals(1, seq.findPosition(2)); + assertEquals(2, seq.findPosition(3)); + assertEquals(3, seq.findPosition(4)); + assertEquals(3, seq.findPosition(5)); + assertEquals(4, seq.findPosition(6)); + assertEquals(4, seq.findPosition(7)); + assertEquals(5, seq.findPosition(8)); + assertEquals(6, seq.findPosition(9)); + assertEquals(7, seq.findPosition(10)); + assertEquals(7, seq.findPosition(11)); + } + + @Test + public void testDeleteChars() + { + SequenceI seq = new Sequence("test", "ABCDEF"); + assertEquals(1, seq.getStart()); + assertEquals(6, seq.getEnd()); + seq.deleteChars(2, 3); + assertEquals("ABDEF", seq.getSequenceAsString()); + assertEquals(1, seq.getStart()); + assertEquals(5, seq.getEnd()); + + seq = new Sequence("test", "ABCDEF"); + seq.deleteChars(0, 2); + assertEquals("CDEF", seq.getSequenceAsString()); + assertEquals(3, seq.getStart()); + assertEquals(6, seq.getEnd()); + } + + @Test + public void testInsertCharAt() + { + // non-static methods: + SequenceI seq = new Sequence("test", "ABCDEF"); + seq.insertCharAt(0, 'z'); + assertEquals("zABCDEF", seq.getSequenceAsString()); + seq.insertCharAt(2, 2, 'x'); + assertEquals("zAxxBCDEF", seq.getSequenceAsString()); + + // for static method see StringUtilsTest + } } diff --git a/test/jalview/util/ComparisonTest.java b/test/jalview/util/ComparisonTest.java new file mode 100644 index 0000000..bfc2610 --- /dev/null +++ b/test/jalview/util/ComparisonTest.java @@ -0,0 +1,89 @@ +package jalview.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; + +import org.junit.Test; + +public class ComparisonTest +{ + + @Test + public void testIsGap() + { + assertTrue(Comparison.isGap('-')); + assertTrue(Comparison.isGap('.')); + assertTrue(Comparison.isGap(' ')); + assertFalse(Comparison.isGap('X')); + assertFalse(Comparison.isGap('x')); + assertFalse(Comparison.isGap('*')); + assertFalse(Comparison.isGap('G')); + } + + /** + * Test for isNucleotide is that sequences in a dataset are more than 85% + * AGCTU. Test is not case-sensitive and ignores gaps. + */ + @Test + public void testIsNucleotide() { + SequenceI seq = new Sequence("eightypercent", "agctuAGCPV"); + assertFalse(Comparison.isNucleotide(new SequenceI[] + { seq })); + + seq = new Sequence("eightyfivepercent", "agctuAGCPVagctuAGCUV"); + assertFalse(Comparison.isNucleotide(new SequenceI[] + { seq })); + + seq = new Sequence("nineypercent", "agctuAGCgVagctuAGCUV"); + assertTrue(Comparison.isNucleotide(new SequenceI[] + { seq })); + + seq = new Sequence("eightyfivepercentgapped", + "--agc--tuA--GCPV-a---gct-uA-GC---UV"); + assertFalse(Comparison.isNucleotide(new SequenceI[] + { seq })); + + seq = new Sequence("nineypercentgapped", + "ag--ct-u-A---GC---g----Vag--c---tuAGCUV"); + assertTrue(Comparison.isNucleotide(new SequenceI[] + { seq })); + + seq = new Sequence("allgap", "---------"); + assertFalse(Comparison.isNucleotide(new SequenceI[] + { seq })); + + seq = new Sequence("DNA", "ACTugGCCAG"); + SequenceI seq2 = new Sequence("Protein", "FLIMVSPTYW"); + assertTrue(Comparison.isNucleotide(new SequenceI[] + { seq, seq, seq, seq, seq, seq, seq, seq, seq, seq2 })); // 90% DNA + assertFalse(Comparison.isNucleotide(new SequenceI[] + { seq, seq, seq, seq, seq, seq, seq, seq, seq2, seq2 })); // 80% DNA + + seq = new Sequence("ProteinThatLooksLikeDNA", "WYATGCCTGAgtcgt"); + // 12/14 = 85.7% + assertTrue(Comparison.isNucleotide(new SequenceI[] + { seq })); + + assertFalse(Comparison.isNucleotide(null)); + } + + /** + * Test percentage identity calculation for two sequences. + */ + @Test + public void testPID_matchGaps() + { + String seq1 = "ABCDEF"; + String seq2 = "abcdef"; + assertEquals("identical", 100f, Comparison.PID(seq1, seq2), 0.001f); + + // comparison range defaults to length of first sequence + seq2 = "abcdefghijklmnopqrstuvwxyz"; + assertEquals("identical", 100f, Comparison.PID(seq1, seq2), 0.001f); + + seq2 = "a---bcdef"; + } +} diff --git a/test/jalview/util/StringUtilsTest.java b/test/jalview/util/StringUtilsTest.java new file mode 100644 index 0000000..eba2da4 --- /dev/null +++ b/test/jalview/util/StringUtilsTest.java @@ -0,0 +1,56 @@ +package jalview.util; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; + +public class StringUtilsTest +{ + + @Test + public void testInsertCharAt() + { + char[] c1 = "ABC".toCharArray(); + char[] expected = new char[] + { 'A', 'B', 'C', 'w', 'w' }; + assertTrue(Arrays.equals(expected, + StringUtils.insertCharAt(c1, 3, 2, 'w'))); + expected = new char[] + { 'A', 'B', 'C', 'w', 'w' }; + assertTrue(Arrays.equals(expected, + StringUtils.insertCharAt(c1, 4, 2, 'w'))); + assertTrue(Arrays.equals(expected, + StringUtils.insertCharAt(c1, 5, 2, 'w'))); + assertTrue(Arrays.equals(expected, + StringUtils.insertCharAt(c1, 6, 2, 'w'))); + assertTrue(Arrays.equals(expected, + StringUtils.insertCharAt(c1, 7, 2, 'w'))); + } + + @Test + public void testDeleteChars() + { + char[] c1 = "ABC".toCharArray(); + + // delete second position + assertTrue(Arrays.equals(new char[] + { 'A', 'C' }, StringUtils.deleteChars(c1, 1, 2))); + + // delete positions 1 and 2 + assertTrue(Arrays.equals(new char[] + { 'C' }, StringUtils.deleteChars(c1, 0, 2))); + + // delete positions 1-3 + assertTrue(Arrays.equals(new char[] + {}, StringUtils.deleteChars(c1, 0, 3))); + + // delete position 3 + assertTrue(Arrays.equals(new char[] + { 'A', 'B' }, StringUtils.deleteChars(c1, 2, 3))); + + // out of range deletion is ignore + assertTrue(Arrays.equals(c1, StringUtils.deleteChars(c1, 3, 4))); + } +}