From 49ff786d8e7d016cac6895539e6fb0eac8aae95c Mon Sep 17 00:00:00 2001 From: gmungoc Date: Thu, 19 Feb 2015 10:31:38 +0000 Subject: [PATCH] JAL-845 applet colour by tree; translate as cDNA; pull up history list --- resources/lang/Messages.properties | 6 +- src/jalview/analysis/AlignmentUtils.java | 18 +- src/jalview/appletgui/APopupMenu.java | 5 +- src/jalview/appletgui/AlignFrame.java | 44 +-- src/jalview/appletgui/AlignViewport.java | 63 +++- src/jalview/appletgui/EmbmenuFrame.java | 46 +-- src/jalview/appletgui/FontChooser.java | 27 +- src/jalview/appletgui/RedundancyPanel.java | 4 +- src/jalview/appletgui/SeqPanel.java | 58 ++- src/jalview/appletgui/SplitFrame.java | 44 ++- src/jalview/appletgui/TreeCanvas.java | 33 +- src/jalview/bin/JalviewLite.java | 1 + src/jalview/datamodel/SequenceGroup.java | 3 + src/jalview/gui/AlignFrame.java | 11 +- src/jalview/gui/AlignViewport.java | 182 ++++------ src/jalview/gui/AlignmentPanel.java | 10 +- src/jalview/gui/Finder.java | 5 +- src/jalview/gui/Jalview2XML.java | 2 +- src/jalview/gui/SplitFrame.java | 26 ++ src/jalview/gui/TreeCanvas.java | 3 +- src/jalview/gui/TreePanel.java | 5 +- src/jalview/io/BioJsHTMLOutput.java | 4 +- src/jalview/util/MappingUtils.java | 19 +- src/jalview/viewmodel/AlignmentViewport.java | 88 ++++- test/jalview/datamodel/SequenceTest.java | 48 +++ test/jalview/ext/rbvi/chimera/ChimeraConnect.java | 12 +- test/jalview/gui/PaintRefresherTest.java | 3 +- test/jalview/util/MappingUtilsTest.java | 400 +++++++++++++++++++++ 28 files changed, 938 insertions(+), 232 deletions(-) create mode 100644 test/jalview/util/MappingUtilsTest.java diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 801d7a0..3499fdd 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -485,8 +485,6 @@ label.load_associated_tree = Load Associated Tree ... label.load_features_annotations = Load Features/Annotations ... label.export_features = Export Features ... label.export_annotations = Export Annotations ... -label.jalview_copy = Copy (Jalview Only) -label.jalview_cut = Cut (Jalview Only) label.to_upper_case = To Upper Case label.to_lower_case = To Lower Case label.toggle_case = Toggle Case @@ -694,7 +692,7 @@ label.save_png_image = Save As PNG Image label.load_tree_for_sequence_set = Load a tree for this sequence set label.export_image = Export Image label.vamsas_store = VAMSAS store -label.translate_cDNA = Translate cDNA +label.translate_cDNA = Translate as cDNA label.cdna = cDNA label.link_cdna = Link cDNA label.link_cdna_tip = Link to any compatible cDNA alignments.
Sequences are linked that have the same name and compatible lengths. @@ -706,7 +704,7 @@ label.align_cdna_tip = Any linked cDNA sequences will be realigned to match this label.cdna_aligned = {0} sequences in {1} alignments were realigned label.view_as_cdna = Show aligned cDNA label.view_as_cdna_tip = Open a new alignment of the related cDNA sequences -label.linked_view_title = {0} and {1} +label.linked_view_title = Linked cDNA and protein view label.align = Align label.extract_scores = Extract Scores label.get_cross_refs = Get Cross References diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index 7116af9..0441b1d 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -218,6 +218,11 @@ public class AlignmentUtils final AlignmentI proteinAlignment, final AlignmentI cdnaAlignment) { + if (proteinAlignment == null || cdnaAlignment == null) + { + return MappingResult.NotMapped; + } + boolean mappingPossible = false; boolean mappingPerformed = false; @@ -305,10 +310,15 @@ public class AlignmentUtils public static MapList mapProteinToCdna(SequenceI proteinSeq, SequenceI cdnaSeq) { - String aaSeqString = proteinSeq.getDatasetSequence() - .getSequenceAsString(); - String cdnaSeqString = cdnaSeq.getDatasetSequence() - .getSequenceAsString(); + /* + * Here we handle either dataset sequence set (desktop) or absent (applet) + */ + final SequenceI proteinDataset = proteinSeq.getDatasetSequence(); + String aaSeqString = proteinDataset != null ? proteinDataset + .getSequenceAsString() : proteinSeq.getSequenceAsString(); + final SequenceI cdnaDataset = cdnaSeq.getDatasetSequence(); + String cdnaSeqString = cdnaDataset != null ? cdnaDataset + .getSequenceAsString() : cdnaSeq.getSequenceAsString(); if (aaSeqString == null || cdnaSeqString == null) { return null; diff --git a/src/jalview/appletgui/APopupMenu.java b/src/jalview/appletgui/APopupMenu.java index bb290d4..38a0f58 100644 --- a/src/jalview/appletgui/APopupMenu.java +++ b/src/jalview/appletgui/APopupMenu.java @@ -114,10 +114,9 @@ public class APopupMenu extends java.awt.PopupMenu implements Menu editMenu = new Menu(MessageManager.getString("action.edit")); - MenuItem copy = new MenuItem( - MessageManager.getString("label.jalview_copy")); + MenuItem copy = new MenuItem(MessageManager.getString("action.copy")); - MenuItem cut = new MenuItem(MessageManager.getString("label.jalview_cut")); + MenuItem cut = new MenuItem(MessageManager.getString("action.cut")); MenuItem toUpper = new MenuItem( MessageManager.getString("label.to_upper_case")); diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index aa7aed3..cd6c9e5 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -92,6 +92,7 @@ import java.io.IOException; import java.net.URL; import java.net.URLEncoder; import java.util.Arrays; +import java.util.Deque; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -1433,12 +1434,12 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, void updateEditMenuBar() { - if (viewport.historyList.size() > 0) + if (viewport.getHistoryList().size() > 0) { undoMenuItem.setEnabled(true); - CommandI command = (CommandI) viewport.historyList.peek(); + CommandI command = viewport.getHistoryList().peek(); undoMenuItem.setLabel(MessageManager.formatMessage( - "label.undo_command", new String[] + "label.undo_command", new Object[] { command.getDescription() })); } else @@ -1447,13 +1448,13 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, undoMenuItem.setLabel(MessageManager.getString("action.undo")); } - if (viewport.redoList.size() > 0) + if (viewport.getRedoList().size() > 0) { redoMenuItem.setEnabled(true); - CommandI command = (CommandI) viewport.redoList.peek(); + CommandI command = viewport.getRedoList().peek(); redoMenuItem.setLabel(MessageManager.formatMessage( - "label.redo_command", new String[] + "label.redo_command", new Object[] { command.getDescription() })); } else @@ -1471,8 +1472,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, { if (command.getSize() > 0) { - viewport.historyList.push(command); - viewport.redoList.removeAllElements(); + viewport.addToHistoryList(command); + viewport.clearRedoList(); updateEditMenuBar(); viewport.updateHiddenColumns(); } @@ -1486,13 +1487,13 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, */ protected void undoMenuItem_actionPerformed() { - if (viewport.historyList.size() < 1) + if (viewport.getHistoryList().isEmpty()) { return; } - CommandI command = (CommandI) viewport.historyList.pop(); - viewport.redoList.push(command); + CommandI command = viewport.getHistoryList().pop(); + viewport.addToRedoList(command); command.undoCommand(null); AlignViewport originalSource = getOriginatingSource(command); @@ -1518,13 +1519,13 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, */ protected void redoMenuItem_actionPerformed() { - if (viewport.redoList.size() < 1) + if (viewport.getRedoList().isEmpty()) { return; } - CommandI command = (CommandI) viewport.redoList.pop(); - viewport.historyList.push(command); + CommandI command = viewport.getRedoList().pop(); + viewport.addToHistoryList(command); command.doCommand(null); AlignViewport originalSource = getOriginatingSource(command); @@ -1681,11 +1682,12 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, } boolean appendHistoryItem = false; - if (viewport.historyList != null && viewport.historyList.size() > 0 - && viewport.historyList.peek() instanceof SlideSequencesCommand) + Deque historyList = viewport.getHistoryList(); + if (historyList != null && historyList.size() > 0 + && historyList.peek() instanceof SlideSequencesCommand) { appendHistoryItem = ssc - .appendSlideCommand((SlideSequencesCommand) viewport.historyList + .appendSlideCommand((SlideSequencesCommand) historyList .peek()); } @@ -2290,8 +2292,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, newaf.setTitle(title.toString()); - newaf.viewport.historyList = viewport.historyList; - newaf.viewport.redoList = viewport.redoList; + newaf.viewport.setHistoryList(viewport.getHistoryList()); + newaf.viewport.setRedoList(viewport.getRedoList()); return newaf; } @@ -3592,8 +3594,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, // view cannot be closed if its actually on the page fileMenu.remove(closeMenuItem); fileMenu.remove(3); // Remove Separator - embeddedMenu = makeEmbeddedPopupMenu(alignFrameMenuBar, - FONT_ARIAL_PLAIN_11, false, false); // use our own fonts. + // construct embedded menu, using default font + embeddedMenu = makeEmbeddedPopupMenu(alignFrameMenuBar, false, false); // and actually add the components to the applet area theApplet.setLayout(new BorderLayout()); theApplet.add(embeddedMenu, BorderLayout.NORTH); diff --git a/src/jalview/appletgui/AlignViewport.java b/src/jalview/appletgui/AlignViewport.java index 156c517..58b2644 100644 --- a/src/jalview/appletgui/AlignViewport.java +++ b/src/jalview/appletgui/AlignViewport.java @@ -23,6 +23,7 @@ package jalview.appletgui; import jalview.analysis.NJTree; import jalview.api.AlignViewportI; import jalview.bin.JalviewLite; +import jalview.commands.CommandI; import jalview.datamodel.AlignmentI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.Sequence; @@ -30,15 +31,16 @@ 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; import jalview.viewmodel.AlignmentViewport; import java.awt.Font; -import java.util.Stack; public class AlignViewport extends AlignmentViewport implements - AlignViewportI, SelectionSource, VamsasSource + AlignViewportI, SelectionSource, VamsasSource, CommandListener { int startRes; @@ -60,10 +62,6 @@ public class AlignViewport extends AlignmentViewport implements boolean MAC = false; - Stack historyList = new Stack(); - - Stack redoList = new Stack(); - private AnnotationColumnChooser annotationColumnSelectionState; public void finalize() @@ -395,13 +393,25 @@ public class AlignViewport extends AlignmentViewport implements public void sendSelection() { - jalview.structure.StructureSelectionManager - .getStructureSelectionManager(applet).sendSelection( + getStructureSelectionManager().sendSelection( new SequenceGroup(getSelectionGroup()), new ColumnSelection(getColumnSelection()), this); } /** + * Returns an instance of the StructureSelectionManager scoped to this applet + * instance. + * + * @return + */ + @Override + public StructureSelectionManager getStructureSelectionManager() + { + return jalview.structure.StructureSelectionManager + .getStructureSelectionManager(applet); + } + + /** * synthesize a column selection if none exists so it covers the given * selection group. if wholewidth is false, no column selection is made if the * selection group covers the whole alignment width. @@ -464,4 +474,41 @@ public class AlignViewport extends AlignmentViewport implements this.annotationColumnSelectionState = annotationColumnSelectionState; } + @Override + public void mirrorCommand(CommandI command, boolean undo, + StructureSelectionManager ssm, VamsasSource source) + { + // TODO refactor so this can be pulled up to superclass or controller + /* + * Do nothing unless we are a 'complement' of the source. May replace this + * with direct calls not via SSM. + */ + if (source instanceof AlignViewportI + && ((AlignViewportI) source).getCodingComplement() == this) + { + // ok to continue; + } + else + { + return; + } + + CommandI mappedCommand = ssm.mapCommand(command, undo, getAlignment(), + getGapCharacter()); + if (mappedCommand != null) + { + mappedCommand.doCommand(null); + firePropertyChange("alignment", null, getAlignment().getSequences()); + + // ap.scalePanelHolder.repaint(); + // ap.repaint(); + } + } + + @Override + public VamsasSource getVamsasSource() + { + return this; + } + } diff --git a/src/jalview/appletgui/EmbmenuFrame.java b/src/jalview/appletgui/EmbmenuFrame.java index 4eb3086..a4c1006 100644 --- a/src/jalview/appletgui/EmbmenuFrame.java +++ b/src/jalview/appletgui/EmbmenuFrame.java @@ -20,8 +20,6 @@ */ package jalview.appletgui; -import jalview.util.MessageManager; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; @@ -35,8 +33,8 @@ import java.awt.Panel; import java.awt.PopupMenu; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.util.Enumeration; -import java.util.Hashtable; +import java.util.HashMap; +import java.util.Map; /** * This class implements a pattern for embedding toolbars as a panel with popups @@ -56,10 +54,12 @@ public class EmbmenuFrame extends Frame implements MouseListener protected static final Font FONT_ARIAL_PLAIN_11 = new Font( "Arial", Font.PLAIN, 11); + public static final Font DEFAULT_MENU_FONT = FONT_ARIAL_PLAIN_11; + /** * map from labels to popup menus for the embedded menubar */ - protected Hashtable embeddedPopup; + protected Map embeddedPopup = new HashMap(); /** * the embedded menu is built on this and should be added to the frame at the @@ -97,9 +97,8 @@ public class EmbmenuFrame extends Frame implements MouseListener // DEBUG Hint: can test embedded menus by inserting true here. if (new jalview.util.Platform().isAMac()) { - // Build the embedded menu panel - embeddedMenu = makeEmbeddedPopupMenu(topMenuBar, FONT_ARIAL_PLAIN_11, - true, false); // try to pickup system font. + // Build the embedded menu panel, allowing override with system font + embeddedMenu = makeEmbeddedPopupMenu(topMenuBar, true, false); setMenuBar(null); // add the components to the Panel area. add(embeddedMenu, BorderLayout.NORTH); @@ -116,34 +115,25 @@ public class EmbmenuFrame extends Frame implements MouseListener * menuBar from the Frame if it is already attached. * * @param menuBar - * @param font * @param overrideFonts * @param append * true means existing menu will be emptied before adding new * elements * @return */ - protected Panel makeEmbeddedPopupMenu(MenuBar menuBar, Font font, + protected Panel makeEmbeddedPopupMenu(MenuBar menuBar, boolean overrideFonts, boolean append) { if (!append) { - if (embeddedPopup != null) - { - embeddedPopup.clear(); // TODO: check if j1.1 - } + embeddedPopup.clear(); // TODO: check if j1.1 if (embeddedMenu != null) { embeddedMenu.removeAll(); } } - if (embeddedPopup == null) - { - embeddedPopup = new Hashtable(); - } - - embeddedMenu = makeEmbeddedPopupMenu(menuBar, font, - overrideFonts, embeddedPopup, new Panel(), this); + embeddedMenu = makeEmbeddedPopupMenu(menuBar, DEFAULT_MENU_FONT, + overrideFonts, new Panel(), this); return embeddedMenu; } @@ -158,8 +148,6 @@ public class EmbmenuFrame extends Frame implements MouseListener * must be non-null * @param font * @param overrideFonts - * @param embeddedPopup - * must be non-null * @param embeddedMenu * if null, a new panel will be created and returned * @param clickHandler @@ -169,13 +157,9 @@ public class EmbmenuFrame extends Frame implements MouseListener */ protected Panel makeEmbeddedPopupMenu(MenuBar menuBar, Font font, boolean overrideFonts, - Hashtable embeddedPopup, Panel embeddedMenu, + Panel embeddedMenu, MouseListener clickHandler) { - if (embeddedPopup == null) - { - throw new Error(MessageManager.getString("error.implementation_error_embeddedpopup_not_null")); - } if (overrideFonts) { Font mbf = menuBar.getFont(); @@ -237,7 +221,7 @@ public class EmbmenuFrame extends Frame implements MouseListener */ PopupMenu getPopupMenu(Label source) { - return (PopupMenu) embeddedPopup.get(source); + return embeddedPopup.get(source); } public void mouseClicked(MouseEvent evt) @@ -264,10 +248,8 @@ public class EmbmenuFrame extends Frame implements MouseListener { if (embeddedPopup != null) { - Enumeration e = embeddedPopup.keys(); - while (e.hasMoreElements()) + for (Label lb : embeddedPopup.keySet()) { - Label lb = (Label) e.nextElement(); lb.removeMouseListener(this); } embeddedPopup.clear(); diff --git a/src/jalview/appletgui/FontChooser.java b/src/jalview/appletgui/FontChooser.java index 6bc67b5..b422cac 100644 --- a/src/jalview/appletgui/FontChooser.java +++ b/src/jalview/appletgui/FontChooser.java @@ -20,10 +20,24 @@ */ package jalview.appletgui; +import jalview.api.ViewStyleI; import jalview.util.MessageManager; -import java.awt.*; -import java.awt.event.*; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Choice; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Frame; +import java.awt.Label; +import java.awt.Panel; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; public class FontChooser extends Panel implements ActionListener, ItemListener @@ -34,6 +48,8 @@ public class FontChooser extends Panel implements ActionListener, Font oldFont; + int oldCharWidth = 0; + boolean init = true; Frame frame; @@ -65,6 +81,7 @@ public class FontChooser extends Panel implements ActionListener, this.ap = ap; oldFont = ap.av.getFont(); + oldCharWidth = ap.av.getViewStyle().getCharWidth(); init(); } @@ -144,6 +161,12 @@ public class FontChooser extends Panel implements ActionListener, if (ap != null) { ap.av.setFont(oldFont); + ViewStyleI style = ap.av.getViewStyle(); + if (style.getCharWidth() != oldCharWidth) + { + style.setCharWidth(oldCharWidth); + ap.av.setViewStyle(style); + } ap.paintAlignment(true); } else if (tp != null) diff --git a/src/jalview/appletgui/RedundancyPanel.java b/src/jalview/appletgui/RedundancyPanel.java index 8e364f0..a81ffd4 100644 --- a/src/jalview/appletgui/RedundancyPanel.java +++ b/src/jalview/appletgui/RedundancyPanel.java @@ -233,9 +233,9 @@ public class RedundancyPanel extends SliderPanel implements Runnable, CommandI command = (CommandI) historyList.pop(); command.undoCommand(null); - if (ap.av.historyList.contains(command)) + if (ap.av.getHistoryList().contains(command)) { - ap.av.historyList.removeElement(command); + ap.av.getHistoryList().remove(command); ap.alignFrame.updateEditMenuBar(); ap.av.firePropertyChange("alignment", null, ap.av.getAlignment().getSequences()); } diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index cf0a047..bea86b2 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -20,6 +20,7 @@ */ package jalview.appletgui; +import jalview.api.AlignViewportI; import jalview.commands.EditCommand; import jalview.commands.EditCommand.Action; import jalview.datamodel.ColumnSelection; @@ -29,10 +30,12 @@ import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.schemes.ResidueProperties; +import jalview.structure.SelectionListener; import jalview.structure.SelectionSource; import jalview.structure.SequenceListener; import jalview.structure.StructureSelectionManager; import jalview.structure.VamsasSource; +import jalview.util.MappingUtils; import jalview.util.MessageManager; import java.awt.BorderLayout; @@ -47,7 +50,7 @@ import java.awt.event.MouseMotionListener; import java.util.Vector; public class SeqPanel extends Panel implements MouseMotionListener, - MouseListener, SequenceListener + MouseListener, SequenceListener, SelectionListener { public SeqCanvas seqCanvas; @@ -110,6 +113,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, seqCanvas.addMouseListener(this); ssm = StructureSelectionManager.getStructureSelectionManager(av.applet); ssm.addStructureViewerListener(this); + ssm.addSelectionListener(this); seqCanvas.repaint(); } @@ -1729,6 +1733,16 @@ public class SeqPanel extends Panel implements MouseMotionListener, { return; } + + /* + * Check for selection in a view of which this one is a dna/protein + * complement. + */ + if (selectionFromTranslation(seqsel, colsel, source)) + { + return; + } + // do we want to thread this ? (contention with seqsel and colsel locks, I // suspect) // rules are: colsel is copied if there is a real intersection between @@ -1846,4 +1860,46 @@ public class SeqPanel extends Panel implements MouseMotionListener, ap.scrollTo(column, column, ap.av.startSeq, true, true); } + /** + * If this panel is a cdna/protein translation view of the selection source, + * tries to map the source selection to a local one, and returns true. Else + * returns false. + * + * @param seqsel + * @param colsel + * @param source + */ + protected boolean selectionFromTranslation(SequenceGroup seqsel, + ColumnSelection colsel, SelectionSource source) + { + if (!(source instanceof AlignViewportI)) { + return false; + } + final AlignViewportI sourceAv = (AlignViewportI) source; + if (sourceAv.getCodingComplement() != av && av.getCodingComplement() != sourceAv) + { + return false; + } + + /* + * Map sequence selection + */ + SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av); + av.setSelectionGroup(sg); + av.isSelectionGroupChanged(true); + + /* + * Map column selection + */ + ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv, + av); + av.setColumnSelection(cs); + av.isColSelChanged(true); + + firePropertyChange("alignment", null, av.getAlignment().getSequences()); + // PaintRefresher.Refresh(this, av.getSequenceSetId()); + + return true; + } + } diff --git a/src/jalview/appletgui/SplitFrame.java b/src/jalview/appletgui/SplitFrame.java index d77f331..a012092 100644 --- a/src/jalview/appletgui/SplitFrame.java +++ b/src/jalview/appletgui/SplitFrame.java @@ -1,6 +1,11 @@ package jalview.appletgui; +import jalview.analysis.AlignmentUtils; +import jalview.analysis.AlignmentUtils.MappingResult; +import jalview.api.ViewStyleI; import jalview.bin.JalviewLite; +import jalview.datamodel.AlignmentI; +import jalview.structure.StructureSelectionManager; import java.awt.BorderLayout; import java.awt.GridLayout; @@ -45,6 +50,42 @@ public class SplitFrame extends EmbmenuFrame addAlignFrameComponents(topFrame, topPanel); addAlignFrameComponents(bottomFrame, bottomPanel); + + /* + * Try to make and add dna/protein sequence mappings + */ + final AlignViewport topViewport = topFrame.viewport; + final AlignViewport bottomViewport = bottomFrame.viewport; + final AlignmentI topAlignment = topViewport.getAlignment(); + final AlignmentI bottomAlignment = bottomViewport.getAlignment(); + // topAlignment.setDataset(null); + // bottomAlignment.setDataset(null); + AlignViewport cdna = topAlignment.isNucleotide() ? topViewport + : (bottomAlignment.isNucleotide() ? bottomViewport : null); + AlignViewport protein = !topAlignment.isNucleotide() ? topViewport + : (!bottomAlignment.isNucleotide() ? bottomViewport : null); + MappingResult mapped = AlignmentUtils.mapProteinToCdna( + protein.getAlignment(), cdna.getAlignment()); + if (mapped != MappingResult.NotMapped) + { + final StructureSelectionManager ssm = StructureSelectionManager + .getStructureSelectionManager(topViewport.applet); + ssm.addMappings(protein.getAlignment().getCodonFrames()); + topViewport.setCodingComplement(bottomViewport); + ssm.addCommandListener(cdna); + ssm.addCommandListener(protein); + } + + /* + * Expand protein to 3 times character width of dna + */ + if (protein != null && cdna != null) + { + ViewStyleI vs = protein.getViewStyle(); + vs.setCharWidth(3 * vs.getCharWidth()); + protein.setViewStyle(vs); + } + } /** @@ -58,7 +99,8 @@ public class SplitFrame extends EmbmenuFrame private void addAlignFrameComponents(AlignFrame af, Panel panel) { panel.setLayout(new BorderLayout()); - Panel menuPanel = makeEmbeddedPopupMenu(af.getMenuBar(), FONT_ARIAL_PLAIN_11, true, false); + Panel menuPanel = af + .makeEmbeddedPopupMenu(af.getMenuBar(), true, false); panel.add(menuPanel, BorderLayout.NORTH); panel.add(af.statusBar, BorderLayout.SOUTH); panel.add(af.alignPanel, BorderLayout.CENTER); diff --git a/src/jalview/appletgui/TreeCanvas.java b/src/jalview/appletgui/TreeCanvas.java index e8d8363..6630d8e 100755 --- a/src/jalview/appletgui/TreeCanvas.java +++ b/src/jalview/appletgui/TreeCanvas.java @@ -22,6 +22,7 @@ package jalview.appletgui; import jalview.analysis.Conservation; import jalview.analysis.NJTree; +import jalview.api.AlignViewportI; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; @@ -31,6 +32,7 @@ import jalview.schemes.ColourSchemeProperty; import jalview.schemes.ResidueProperties; import jalview.schemes.UserColourScheme; import jalview.util.Format; +import jalview.util.MappingUtils; import java.awt.Color; import java.awt.Dimension; @@ -600,6 +602,13 @@ public class TreeCanvas extends Panel implements MouseListener, av.setSelectionGroup(null); av.getAlignment().deleteAllGroups(); av.clearSequenceColours(); + final AlignViewportI codingComplement = av.getCodingComplement(); + if (codingComplement != null) + { + codingComplement.setSelectionGroup(null); + codingComplement.getAlignment().deleteAllGroups(); + codingComplement.clearSequenceColours(); + } colourGroups(); @@ -684,9 +693,31 @@ public class TreeCanvas extends Panel implements MouseListener, av.getAlignment().addGroup(sg); + // TODO this is duplicated with gui TreeCanvas - refactor + av.getAlignment().addGroup(sg); + final AlignViewportI codingComplement = av.getCodingComplement(); + if (codingComplement != null) + { + SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av, + codingComplement); + if (mappedGroup.getSequences().size() > 0) + { + codingComplement.getAlignment().addGroup(mappedGroup); + for (SequenceI seq : mappedGroup.getSequences()) + { + // TODO why does gui require col.brighter() here?? + codingComplement.setSequenceColour(seq, col); + } + } + } + } ap.updateAnnotation(); - + if (av.getCodingComplement() != null) + { + ((AlignViewport) av.getCodingComplement()).firePropertyChange( + "alignment", null, ap.av.getAlignment().getSequences()); + } } public void setShowDistances(boolean state) diff --git a/src/jalview/bin/JalviewLite.java b/src/jalview/bin/JalviewLite.java index ca3de8b..a004f24 100644 --- a/src/jalview/bin/JalviewLite.java +++ b/src/jalview/bin/JalviewLite.java @@ -1916,6 +1916,7 @@ public class JalviewLite extends Applet implements if ((al != null) && (al.getHeight() > 0)) { dbgMsg("Successfully loaded file."); + al.setDataset(null); AlignFrame newAlignFrame = new AlignFrame(al, applet, resolvedFile, embedded, false); newAlignFrame.setTitle(resolvedFile); diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index 9c046c6..beb6b51 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -1307,6 +1307,9 @@ public class SequenceGroup implements AnnotatedCollectionI return false; } + /** + * Remove all sequences from the group (leaving other properties unchanged). + */ public void clear() { sequences.clear(); diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 30749a5..38940d2 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -4927,7 +4927,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, /** * Construct and display a new frame containing the translation of this - * frame's cDNA sequences to their aligned protein (amino acid) equivalents. + * frame's DNA sequences to their aligned protein (amino acid) equivalents. */ @Override public void showTranslation_actionPerformed(ActionEvent e) @@ -4964,10 +4964,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT); af.setFileFormat(this.currentFileFormat); - Desktop.addInternalFrame(af, MessageManager.formatMessage( + final String newTitle = MessageManager.formatMessage( "label.translation_of_params", new Object[] - { this.getTitle() }), DEFAULT_WIDTH, DEFAULT_HEIGHT); - // enable next line for linked editing + { this.getTitle() }); + af.setTitle(newTitle); + viewport.openSplitFrame(af, viewport.getAlignment()); + // Desktop.addInternalFrame(af, newTitle, DEFAULT_WIDTH, DEFAULT_HEIGHT); + // // enable next line for linked editing // viewport.getStructureSelectionManager().addCommandListener(viewport); } } diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 6208597..8fc10d7 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -67,10 +67,7 @@ import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Rectangle; -import java.io.File; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.Hashtable; import java.util.Set; import java.util.Vector; @@ -85,7 +82,7 @@ import javax.swing.JOptionPane; * @version $Revision: 1.141 $ */ public class AlignViewport extends AlignmentViewport implements - SelectionSource, VamsasSource, AlignViewportI, CommandListener + SelectionSource, AlignViewportI, CommandListener { int startRes; @@ -112,10 +109,6 @@ public class AlignViewport extends AlignmentViewport implements private boolean gatherViewsHere = false; - private Deque historyList = new ArrayDeque(); - - private Deque redoList = new ArrayDeque(); - private AnnotationColumnChooser annotationColumnSelectionState; /** * Creates a new AlignViewport object. @@ -762,6 +755,10 @@ public class AlignViewport extends AlignmentViewport implements } } + /** + * Returns the (Desktop) instance of the StructureSelectionManager + */ + @Override public StructureSelectionManager getStructureSelectionManager() { return StructureSelectionManager @@ -891,8 +888,8 @@ public class AlignViewport extends AlignmentViewport implements StructureSelectionManager ssm, VamsasSource source) { /* - * ...work in progress... do nothing unless we are a 'complement' of the - * source May replace this with direct calls not via SSM. + * Do nothing unless we are a 'complement' of the source. May replace this + * with direct calls not via SSM. */ if (source instanceof AlignViewportI && ((AlignViewportI) source).getCodingComplement() == this) @@ -914,76 +911,6 @@ public class AlignViewport extends AlignmentViewport implements } } - @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; - } - /** * Add the sequences from the given alignment to this viewport. Optionally, * may give the user the option to open a new frame, or split panel, with cDNA @@ -1103,8 +1030,6 @@ public class AlignViewport extends AlignmentViewport implements * added to the protein alignment. */ MappingResult mapped = AlignmentUtils.mapProteinToCdna(protein, cdna); - final StructureSelectionManager ssm = StructureSelectionManager - .getStructureSelectionManager(Desktop.instance); if (mapped != MappingResult.Mapped) { /* @@ -1128,55 +1053,72 @@ public class AlignViewport extends AlignmentViewport implements if (openSplitPane) { - // TODO: move this kind of constructor stuff to a factory/controller - // method ? - /* - * Open in split pane. DNA sequence above, protein below. - */ - AlignFrame copyMe = new AlignFrame(thisAlignment, - AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); - copyMe.setTitle(getAlignPanel().alignFrame.getTitle()); - final AlignFrame proteinFrame = al.isNucleotide() ? copyMe - : newAlignFrame; - final AlignFrame cdnaFrame = al.isNucleotide() ? newAlignFrame - : copyMe; - protein = proteinFrame.viewport.getAlignment(); - - cdnaFrame.setVisible(true); - proteinFrame.setVisible(true); - String sep = String.valueOf(File.separatorChar); - String proteinShortName = proteinFrame.getTitle().substring( - proteinFrame.getTitle().lastIndexOf(sep) + 1); - String dnaShortName = cdnaFrame.getTitle().substring( - cdnaFrame.getTitle().lastIndexOf(sep) + 1); - String linkedTitle = MessageManager.formatMessage( - "label.linked_view_title", dnaShortName, proteinShortName); - JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame); - Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1); - - /* - * Set the frames to listen for each other's edit and sort commands. - */ - ssm.addCommandListener(cdnaFrame.getViewport()); - ssm.addCommandListener(proteinFrame.getViewport()); - - /* - * 'Coding complement' (dna/protein) views will mirror each others' edits, - * selections, sorting etc as decided from time to time by the relevant - * authorities. - */ - proteinFrame.getViewport().setCodingComplement(cdnaFrame.getViewport()); + protein = openSplitFrame(newAlignFrame, thisAlignment); } /* * Register the mappings (held on the protein alignment) with the * StructureSelectionManager (for mouseover linking). */ + final StructureSelectionManager ssm = StructureSelectionManager + .getStructureSelectionManager(Desktop.instance); ssm.addMappings(protein.getCodonFrames()); return true; } + /** + * Helper method to open a new SplitFrame holding linked dna and protein + * alignments. + * + * @param newAlignFrame + * containing a new alignment to be shown + * @param existingAlignment + * an existing alignment to be copied for display in the split frame + * @return the protein alignment in the split frame + */ + protected AlignmentI openSplitFrame(AlignFrame newAlignFrame, + AlignmentI existingAlignment) + { + // TODO: move this to a factory/controller method ? + /* + * Open in split pane. DNA sequence above, protein below. + */ + AlignFrame copyMe = new AlignFrame(existingAlignment, + AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); + copyMe.setTitle(getAlignPanel().alignFrame.getTitle()); + + AlignmentI al = newAlignFrame.viewport.getAlignment(); + final AlignFrame proteinFrame = al.isNucleotide() ? copyMe + : newAlignFrame; + final AlignFrame cdnaFrame = al.isNucleotide() ? newAlignFrame + : copyMe; + AlignmentI protein = proteinFrame.viewport.getAlignment(); + + cdnaFrame.setVisible(true); + proteinFrame.setVisible(true); + String linkedTitle = MessageManager + .getString("label.linked_view_title"); + JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame); + Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1); + + /* + * Set the frames to listen for each other's edit and sort commands. + */ + final StructureSelectionManager ssm = StructureSelectionManager + .getStructureSelectionManager(Desktop.instance); + ssm.addCommandListener(cdnaFrame.getViewport()); + ssm.addCommandListener(proteinFrame.getViewport()); + + /* + * 'Coding complement' (dna/protein) views will mirror each others' edits, + * selections, sorting etc as decided from time to time by the relevant + * authorities. + */ + proteinFrame.getViewport().setCodingComplement(cdnaFrame.getViewport()); + return protein; + } + public AnnotationColumnChooser getAnnotationColumnSelectionState() { return annotationColumnSelectionState; diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index d0da010..7132ff0 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -1328,24 +1328,24 @@ public class AlignmentPanel extends GAlignmentPanel implements for (res = 0; res < alwidth; res++) { text = new StringBuffer(); - Object obj = null; + String triplet = null; if (av.getAlignment().isNucleotide()) { - obj = ResidueProperties.nucleotideName.get(seq.getCharAt(res) + triplet = ResidueProperties.nucleotideName.get(seq + .getCharAt(res) + ""); } else { - obj = ResidueProperties.aa2Triplet.get(seq.getCharAt(res) + triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res) + ""); } - if (obj == null) + if (triplet == null) { continue; } - String triplet = obj.toString(); int alIndex = seq.findPosition(res); gSize = groups.length; for (g = 0; g < gSize; g++) diff --git a/src/jalview/gui/Finder.java b/src/jalview/gui/Finder.java index 6f3e7b8..3cc82c5 100755 --- a/src/jalview/gui/Finder.java +++ b/src/jalview/gui/Finder.java @@ -25,6 +25,7 @@ import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.jbgui.GFinder; import jalview.util.MessageManager; +import jalview.viewmodel.AlignmentViewport; import java.awt.event.ActionEvent; import java.util.Vector; @@ -52,7 +53,7 @@ public class Finder extends GFinder private static final int WIDTH = 340; - AlignViewport av; + AlignmentViewport av; AlignmentPanel ap; @@ -80,7 +81,7 @@ public class Finder extends GFinder * @param viewport * @param alignPanel */ - public Finder(AlignViewport viewport, AlignmentPanel alignPanel) + public Finder(AlignmentViewport viewport, AlignmentPanel alignPanel) { av = viewport; ap = alignPanel; diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index e1d1925..86fbde8 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -3604,7 +3604,7 @@ public class Jalview2XML if (view.getSequenceSetId() != null) { - jalview.gui.AlignViewport av = (jalview.gui.AlignViewport) viewportsAdded + AlignmentViewport av = (AlignmentViewport) viewportsAdded .get(uniqueSeqSetId); af.viewport.setSequenceSetId(uniqueSeqSetId); diff --git a/src/jalview/gui/SplitFrame.java b/src/jalview/gui/SplitFrame.java index 791e04f..c46bcd4 100644 --- a/src/jalview/gui/SplitFrame.java +++ b/src/jalview/gui/SplitFrame.java @@ -1,8 +1,11 @@ package jalview.gui; +import jalview.api.ViewStyleI; +import jalview.datamodel.AlignmentI; import jalview.jbgui.GAlignFrame; import jalview.jbgui.GSplitFrame; import jalview.structure.StructureSelectionManager; +import jalview.viewmodel.AlignmentViewport; import java.awt.Component; import java.awt.Toolkit; @@ -51,6 +54,8 @@ public class SplitFrame extends GSplitFrame { setSize(AlignFrame.DEFAULT_WIDTH, Desktop.instance.getHeight() - 20); + setCharacterWidth(); + addCloseFrameListener(); addKeyListener(); @@ -59,6 +64,27 @@ public class SplitFrame extends GSplitFrame } /** + * Set the character width for protein to 3 times that for dna. + */ + private void setCharacterWidth() + { + final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport; + final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport; + final AlignmentI topAlignment = topViewport.getAlignment(); + final AlignmentI bottomAlignment = bottomViewport.getAlignment(); + AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport + : (bottomAlignment.isNucleotide() ? bottomViewport : null); + AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport + : (!bottomAlignment.isNucleotide() ? bottomViewport : null); + if (protein != null && cdna != null) + { + ViewStyleI vs = cdna.getViewStyle(); + vs.setCharWidth(3 * vs.getCharWidth()); + protein.setViewStyle(vs); + } + } + + /** * Add a listener to tidy up when the frame is closed. */ protected void addCloseFrameListener() diff --git a/src/jalview/gui/TreeCanvas.java b/src/jalview/gui/TreeCanvas.java index d02a3eb..fc0a758 100755 --- a/src/jalview/gui/TreeCanvas.java +++ b/src/jalview/gui/TreeCanvas.java @@ -992,8 +992,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, codingComplement.getAlignment().addGroup(mappedGroup); for (SequenceI seq : mappedGroup.getSequences()) { - codingComplement.setSequenceColour(seq, - mappedGroup.getIdColour().brighter()); + codingComplement.setSequenceColour(seq, col.brighter()); } } } diff --git a/src/jalview/gui/TreePanel.java b/src/jalview/gui/TreePanel.java index d7809cb..cde6d5f 100755 --- a/src/jalview/gui/TreePanel.java +++ b/src/jalview/gui/TreePanel.java @@ -43,6 +43,7 @@ import jalview.io.NewickFile; import jalview.jbgui.GTreePanel; import jalview.schemes.ResidueProperties; import jalview.util.MessageManager; +import jalview.viewmodel.AlignmentViewport; import java.awt.Font; import java.awt.Graphics; @@ -138,7 +139,7 @@ public class TreePanel extends GTreePanel return treeCanvas.av.getAlignment(); } - public AlignViewport getViewPort() + public AlignmentViewport getViewPort() { return treeCanvas.av; } @@ -619,7 +620,7 @@ public class TreePanel extends GTreePanel public CommandI sortAlignmentIn(AlignmentPanel ap) { - AlignViewport av = ap.av; + AlignmentViewport av = ap.av; SequenceI[] oldOrder = av.getAlignment().getSequencesArray(); AlignmentSorter.sortByTree(av.getAlignment(), tree); CommandI undo; diff --git a/src/jalview/io/BioJsHTMLOutput.java b/src/jalview/io/BioJsHTMLOutput.java index db43a3f..ad3b79f 100644 --- a/src/jalview/io/BioJsHTMLOutput.java +++ b/src/jalview/io/BioJsHTMLOutput.java @@ -5,7 +5,6 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.exceptions.NoFileSelectedException; -import jalview.gui.AlignViewport; import jalview.gui.AlignmentPanel; import jalview.gui.FeatureRenderer; import jalview.json.binding.v1.BioJsAlignmentPojo; @@ -13,6 +12,7 @@ import jalview.json.binding.v1.BioJsFeaturePojo; import jalview.json.binding.v1.BioJsSeqPojo; import jalview.schemes.ColourSchemeProperty; import jalview.util.MessageManager; +import jalview.viewmodel.AlignmentViewport; import java.awt.Color; import java.io.BufferedReader; @@ -26,7 +26,7 @@ import com.json.JSONException; public class BioJsHTMLOutput { - private AlignViewport av; + private AlignmentViewport av; private jalview.api.FeatureRenderer fr; diff --git a/src/jalview/util/MappingUtils.java b/src/jalview/util/MappingUtils.java index 6dfebfe..1f2e8db 100644 --- a/src/jalview/util/MappingUtils.java +++ b/src/jalview/util/MappingUtils.java @@ -16,7 +16,6 @@ import jalview.datamodel.SearchResults.Match; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; -import jalview.gui.AlignViewport; import java.util.ArrayList; import java.util.HashMap; @@ -384,8 +383,8 @@ public final class MappingUtils } /* - * Have to align the sequences before constructing the OrderCommand - which - * then realigns them?!? + * Have to sort the sequences before constructing the OrderCommand - which + * then resorts them?!? */ final SequenceI[] mappedOrderArray = mappedOrder .toArray(new SequenceI[mappedOrder.size()]); @@ -407,7 +406,7 @@ public final class MappingUtils * @return */ public static ColumnSelection mapColumnSelection(ColumnSelection colsel, - AlignViewportI mapFrom, AlignViewport mapTo) + AlignViewportI mapFrom, AlignViewportI mapTo) { boolean targetIsNucleotide = mapTo.isNucleotide(); AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo; @@ -416,6 +415,12 @@ public final class MappingUtils ColumnSelection mappedColumns = new ColumnSelection(); char fromGapChar = mapFrom.getAlignment().getGapCharacter(); + // FIXME allow for hidden columns + + /* + * For each mapped column, find the range of columns that residues in that + * column map to. + */ for (Object obj : colsel.getSelected()) { int col = ((Integer) obj).intValue(); @@ -462,13 +467,15 @@ public final class MappingUtils // System.out.println(fromSeq.getName() + " mapped to cols " // + mappedStartCol + ":" + mappedEndCol); break; - // TODO remove break if we ever want to map one to many sequences + // note: remove break if we ever want to map one to many sequences } } } } /* - * Add mapped columns to mapped selection (converting base 1 to base 0) + * Add the range of mapped columns to the mapped selection (converting + * base 1 to base 0). Note that this may include intron-only regions which + * lie between the start and end ranges of the selection. */ for (int i = mappedToMin; i <= mappedToMax; i++) { diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index 67db6fc..4fc587e 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -26,6 +26,7 @@ import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; import jalview.api.FeaturesDisplayedI; import jalview.api.ViewStyleI; +import jalview.commands.CommandI; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentView; @@ -40,14 +41,19 @@ import jalview.schemes.Blosum62ColourScheme; import jalview.schemes.ColourSchemeI; import jalview.schemes.PIDColourScheme; import jalview.schemes.ResidueProperties; +import jalview.structure.CommandListener; +import jalview.structure.StructureSelectionManager; +import jalview.structure.VamsasSource; import jalview.viewmodel.styles.ViewStyle; import jalview.workers.AlignCalcManager; import jalview.workers.ConsensusThread; import jalview.workers.StrucConsensusThread; import java.awt.Color; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.BitSet; +import java.util.Deque; import java.util.HashMap; import java.util.Hashtable; import java.util.List; @@ -61,7 +67,7 @@ import java.util.Map; * */ public abstract class AlignmentViewport implements AlignViewportI, - ViewStyleI + ViewStyleI, CommandListener, VamsasSource { protected ViewStyleI viewStyle = new ViewStyle(); @@ -71,6 +77,12 @@ public abstract class AlignmentViewport implements AlignViewportI, */ AlignViewportI codingComplement = null; + FeaturesDisplayedI featuresDisplayed = null; + + protected Deque historyList = new ArrayDeque(); + + protected Deque redoList = new ArrayDeque(); + /** * @param name * @see jalview.api.ViewStyleI#setFontName(java.lang.String) @@ -1958,8 +1970,6 @@ public abstract class AlignmentViewport implements AlignViewportI, return getAlignment() == null ? false : getAlignment().isNucleotide(); } - FeaturesDisplayedI featuresDisplayed = null; - @Override public FeaturesDisplayedI getFeaturesDisplayed() { @@ -2242,4 +2252,76 @@ public abstract class AlignmentViewport implements AlignViewportI, { viewStyle.setShowNPFeats(shownpfeats); } + + public abstract StructureSelectionManager getStructureSelectionManager(); + + /** + * 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; + } + + @Override + public VamsasSource getVamsasSource() + { + return this; + } } diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index 2a2d2ab..1ef1d07 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -262,4 +262,52 @@ public class SequenceTest seq.createDatasetSequence(); assertEquals("[1, 4, 6, 7, 9, 12]", Arrays.toString(seq.gapMap())); } + + /** + * Test the method that gets sequence features, either from the sequence or + * its dataset. + */ + @Test + public void testGetSequenceFeatures() + { + SequenceI seq = new Sequence("test", "GATCAT"); + seq.createDatasetSequence(); + + assertNull(seq.getSequenceFeatures()); + + /* + * SequenceFeature on sequence + */ + SequenceFeature sf = new SequenceFeature(); + seq.addSequenceFeature(sf); + SequenceFeature[] sfs = seq.getSequenceFeatures(); + assertEquals(1, sfs.length); + assertSame(sf, sfs[0]); + + /* + * SequenceFeature on sequence and dataset sequence; returns that on + * sequence + */ + SequenceFeature sf2 = new SequenceFeature(); + seq.getDatasetSequence().addSequenceFeature(sf2); + sfs = seq.getSequenceFeatures(); + assertEquals(1, sfs.length); + assertSame(sf, sfs[0]); + + /* + * SequenceFeature on dataset sequence only + */ + seq.setSequenceFeatures(null); + sfs = seq.getSequenceFeatures(); + assertEquals(1, sfs.length); + assertSame(sf2, sfs[0]); + + /* + * Corrupt case - no SequenceFeature, dataset's dataset is the original + * sequence. Test shows no infinite loop results. + */ + seq.getDatasetSequence().setSequenceFeatures(null); + seq.getDatasetSequence().setDatasetSequence(seq); // loop! + assertNull(seq.getSequenceFeatures()); + } } diff --git a/test/jalview/ext/rbvi/chimera/ChimeraConnect.java b/test/jalview/ext/rbvi/chimera/ChimeraConnect.java index be4e5ea..ad4f997 100644 --- a/test/jalview/ext/rbvi/chimera/ChimeraConnect.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraConnect.java @@ -1,13 +1,14 @@ package jalview.ext.rbvi.chimera; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; -import java.util.Arrays; import java.util.Collection; import org.junit.Test; -import ext.edu.ucsf.rbvi.strucviz2.*; +import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager; +import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel; +import ext.edu.ucsf.rbvi.strucviz2.StructureManager; public class ChimeraConnect { @@ -15,9 +16,10 @@ public class ChimeraConnect @Test public void test() { - StructureManager csm; + StructureManager csm; ext.edu.ucsf.rbvi.strucviz2.ChimeraManager cm = new ChimeraManager(csm = new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true)); - assertTrue("Couldn't launch chimera",cm.launchChimera(csm.getChimeraPaths())); + assertTrue("Couldn't launch chimera", + cm.launchChimera(StructureManager.getChimeraPaths())); int n=0; while (n++<100) { diff --git a/test/jalview/gui/PaintRefresherTest.java b/test/jalview/gui/PaintRefresherTest.java index 2737dd0..1da7c8c 100644 --- a/test/jalview/gui/PaintRefresherTest.java +++ b/test/jalview/gui/PaintRefresherTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertTrue; import jalview.datamodel.Alignment; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; +import jalview.viewmodel.AlignmentViewport; import java.awt.Component; import java.util.List; @@ -101,7 +102,7 @@ public class PaintRefresherTest * and SeqCanvas, IdPanel, AlignmentPanel are all registered under the * sequence set id of the viewport. */ - AlignViewport av = new AlignViewport(al); + AlignmentViewport av = new AlignViewport(al); AlignFrame af = new AlignFrame(al, 4, 1); AlignmentPanel ap1 = af.alignPanel; AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(av diff --git a/test/jalview/util/MappingUtilsTest.java b/test/jalview/util/MappingUtilsTest.java new file mode 100644 index 0000000..f0f3be7 --- /dev/null +++ b/test/jalview/util/MappingUtilsTest.java @@ -0,0 +1,400 @@ +package jalview.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import jalview.api.AlignViewportI; +import jalview.datamodel.AlignedCodonFrame; +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.SearchResults; +import jalview.datamodel.SearchResults.Match; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceGroup; +import jalview.gui.AlignViewport; +import jalview.io.AppletFormatAdapter; +import jalview.io.FormatAdapter; + +import java.awt.Color; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +import org.junit.Test; + +public class MappingUtilsTest +{ + private AlignViewportI dnaView; + private AlignViewportI proteinView; + + /** + * Simple test of mapping with no intron involved. + */ + @Test + public void testBuildSearchResults() + { + final Sequence seq1 = new Sequence("Seq1", "C-G-TA-GC"); + seq1.createDatasetSequence(); + + final Sequence aseq1 = new Sequence("Seq1", "-P-R"); + aseq1.createDatasetSequence(); + + /* + * Map dna bases 1-6 to protein residues 1-2 + */ + AlignedCodonFrame acf = new AlignedCodonFrame(); + MapList map = new MapList(new int[] + { 1, 6 }, new int[] + { 1, 2 }, 3, 1); + acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map); + Set acfList = Collections.singleton(acf); + + /* + * Check protein residue 1 maps to codon 1-3, 2 to codon 4-6 + */ + SearchResults sr = MappingUtils.buildSearchResults(aseq1, 1, acfList); + assertEquals(1, sr.getResults().size()); + Match m = sr.getResults().get(0); + assertEquals(seq1.getDatasetSequence(), m.getSequence()); + assertEquals(1, m.getStart()); + assertEquals(3, m.getEnd()); + sr = MappingUtils.buildSearchResults(aseq1, 2, acfList); + assertEquals(1, sr.getResults().size()); + m = sr.getResults().get(0); + assertEquals(seq1.getDatasetSequence(), m.getSequence()); + assertEquals(4, m.getStart()); + assertEquals(6, m.getEnd()); + + /* + * Check inverse mappings, from codons 1-3, 4-6 to protein 1, 2 + */ + for (int i = 1; i < 7; i++) + { + sr = MappingUtils.buildSearchResults(seq1, i, acfList); + assertEquals(1, sr.getResults().size()); + m = sr.getResults().get(0); + assertEquals(aseq1.getDatasetSequence(), m.getSequence()); + int residue = i > 3 ? 2 : 1; + assertEquals(residue, m.getStart()); + assertEquals(residue, m.getEnd()); + } + } + + /** + * Simple test of mapping with introns involved. + */ + @Test + public void testBuildSearchResults_withIntro() + { + final Sequence seq1 = new Sequence("Seq1", "C-G-TAGA-GCAGCTT"); + seq1.createDatasetSequence(); + + final Sequence aseq1 = new Sequence("Seq1", "-P-R"); + aseq1.createDatasetSequence(); + + /* + * Map dna bases [2, 4, 5], [7, 9, 11] to protein residues 1 and 2 + */ + AlignedCodonFrame acf = new AlignedCodonFrame(); + MapList map = new MapList(new int[] + { 2, 2, 4, 5, 7, 7, 9, 9, 11, 11 }, new int[] + { 1, 2 }, 3, 1); + acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map); + Set acfList = Collections.singleton(acf); + + /* + * Check protein residue 1 maps to [2, 4, 5] + */ + SearchResults sr = MappingUtils.buildSearchResults(aseq1, 1, acfList); + assertEquals(2, sr.getResults().size()); + Match m = sr.getResults().get(0); + assertEquals(seq1.getDatasetSequence(), m.getSequence()); + assertEquals(2, m.getStart()); + assertEquals(2, m.getEnd()); + m = sr.getResults().get(1); + assertEquals(seq1.getDatasetSequence(), m.getSequence()); + assertEquals(4, m.getStart()); + assertEquals(5, m.getEnd()); + + /* + * Check protein residue 2 maps to [7, 9, 11] + */ + sr = MappingUtils.buildSearchResults(aseq1, 2, acfList); + assertEquals(3, sr.getResults().size()); + m = sr.getResults().get(0); + assertEquals(seq1.getDatasetSequence(), m.getSequence()); + assertEquals(7, m.getStart()); + assertEquals(7, m.getEnd()); + m = sr.getResults().get(1); + assertEquals(seq1.getDatasetSequence(), m.getSequence()); + assertEquals(9, m.getStart()); + assertEquals(9, m.getEnd()); + m = sr.getResults().get(2); + assertEquals(seq1.getDatasetSequence(), m.getSequence()); + assertEquals(11, m.getStart()); + assertEquals(11, m.getEnd()); + + /* + * Check inverse mappings, from codons to protein + */ + for (int i = 1; i < 14; i++) + { + sr = MappingUtils.buildSearchResults(seq1, i, acfList); + int residue = (i == 2 || i == 4 || i == 5) ? 1 : (i == 7 || i == 9 + || i == 11 ? 2 : 0); + if (residue == 0) + { + assertEquals(0, sr.getResults().size()); + continue; + } + assertEquals(1, sr.getResults().size()); + m = sr.getResults().get(0); + assertEquals(aseq1.getDatasetSequence(), m.getSequence()); + assertEquals(residue, m.getStart()); + assertEquals(residue, m.getEnd()); + } + } + + /** + * Test mapping a sequence group. + * + * @throws IOException + */ + @Test + public void testMapSequenceGroup() throws IOException + { + /* + * Set up dna and protein Seq1/2/3 with mappings (held on the protein + * viewport). + */ + AlignmentI cdna = loadAlignment(">Seq1\nACG\n>Seq2\nTGA\n>Seq3\nTAC\n", + "FASTA"); + cdna.setDataset(null); + AlignmentI protein = loadAlignment(">Seq1\nK\n>Seq2\nL\n>Seq3\nQ\n", + "FASTA"); + protein.setDataset(null); + AlignedCodonFrame acf = new AlignedCodonFrame(); + MapList map = new MapList(new int[] + { 1, 3 }, new int[] + { 1, 1 }, 3, 1); + for (int seq = 0; seq < 3; seq++) + { + acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein + .getSequenceAt(seq).getDatasetSequence(), map); + } + Set acfList = Collections.singleton(acf); + + AlignViewportI dnaView = new AlignViewport(cdna); + AlignViewportI proteinView = new AlignViewport(protein); + protein.setCodonFrames(acfList); + + /* + * Select Seq1 and Seq3 in the protein + */ + SequenceGroup sg = new SequenceGroup(); + sg.setColourText(true); + sg.setIdColour(Color.GREEN); + sg.setOutlineColour(Color.LIGHT_GRAY); + sg.addSequence(protein.getSequenceAt(0), false); + sg.addSequence(protein.getSequenceAt(2), false); + + /* + * Verify the mapped sequence group in dna + */ + SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, proteinView, dnaView); + assertTrue(mappedGroup.getColourText()); + assertSame(sg.getIdColour(), mappedGroup.getIdColour()); + assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour()); + assertEquals(2, mappedGroup.getSequences().size()); + assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0)); + assertSame(cdna.getSequenceAt(2), mappedGroup.getSequences().get(1)); + + /* + * Verify mapping sequence group from dna to protein + */ + sg.clear(); + sg.addSequence(cdna.getSequenceAt(1), false); + sg.addSequence(cdna.getSequenceAt(0), false); + mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView); + assertTrue(mappedGroup.getColourText()); + assertSame(sg.getIdColour(), mappedGroup.getIdColour()); + assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour()); + assertEquals(2, mappedGroup.getSequences().size()); + assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(0)); + assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(1)); + } + + /** + * Helper method to load an alignment and ensure dataset sequences are set up. + * + * @param data + * @param format + * TODO + * @return + * @throws IOException + */ + protected AlignmentI loadAlignment(final String data, String format) + throws IOException + { + Alignment a = new FormatAdapter().readFile(data, + AppletFormatAdapter.PASTE, format); + a.setDataset(null); + return a; + } + + /** + * Test mapping a column selection in protein to its dna equivalent + * + * @throws IOException + */ + @Test + public void testMapColumnSelection_proteinToDna() throws IOException + { + setupMappedAlignments(); + + ColumnSelection colsel = new ColumnSelection(); + + /* + * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3 + * in dna respectively, overall 0-4 + */ + colsel.addElement(0); + ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, + proteinView, dnaView); + assertEquals("[0, 1, 2, 3, 4]", cs.getSelected().toString()); + + /* + * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna + */ + colsel.clear(); + colsel.addElement(1); + cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView); + assertEquals("[0, 1, 2, 3]", cs.getSelected().toString()); + + /* + * Column 2 in protein picks up gaps only - no mapping + */ + colsel.clear(); + colsel.addElement(2); + cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView); + assertEquals("[]", cs.getSelected().toString()); + + /* + * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns + * 6-9, 6-10, 5-8 respectively, overall to 5-10 + */ + colsel.clear(); + colsel.addElement(3); + cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView); + assertEquals("[5, 6, 7, 8, 9, 10]", cs.getSelected().toString()); + + /* + * Combine selection of columns 1 and 3 to get a discontiguous mapped + * selection + */ + colsel.clear(); + colsel.addElement(1); + colsel.addElement(3); + cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView); + assertEquals("[0, 1, 2, 3, 5, 6, 7, 8, 9, 10]", cs.getSelected() + .toString()); + } + + /** + * @throws IOException + */ + protected void setupMappedAlignments() throws IOException + { + /* + * Set up dna and protein Seq1/2/3 with mappings (held on the protein + * viewport). Lower case for introns. + */ + AlignmentI cdna = loadAlignment(">Seq1\nAC-GctGtC-T\n" + + ">Seq2\nTc-GA-G-T-Tc\n" + ">Seq3\nTtTT-AaCGg-\n", + "FASTA"); + cdna.setDataset(null); + AlignmentI protein = loadAlignment( + ">Seq1\n-K-P\n>Seq2\nL--Q\n>Seq3\nG--S\n", + "FASTA"); + protein.setDataset(null); + AlignedCodonFrame acf = new AlignedCodonFrame(); + MapList map = new MapList(new int[] + { 1, 3, 6, 6, 8, 9 }, new int[] + { 1, 2 }, 3, 1); + acf.addMap(cdna.getSequenceAt(0).getDatasetSequence(), protein + .getSequenceAt(0).getDatasetSequence(), map); + map = new MapList(new int[] + { 1, 1, 3, 4, 5, 7 }, new int[] + { 1, 2 }, 3, 1); + acf.addMap(cdna.getSequenceAt(1).getDatasetSequence(), protein + .getSequenceAt(1).getDatasetSequence(), map); + map = new MapList(new int[] + { 1, 1, 3, 4, 5, 5, 7, 8 }, new int[] + { 1, 2 }, 3, 1); + acf.addMap(cdna.getSequenceAt(2).getDatasetSequence(), protein + .getSequenceAt(2).getDatasetSequence(), map); + Set acfList = Collections.singleton(acf); + + dnaView = new AlignViewport(cdna); + proteinView = new AlignViewport(protein); + protein.setCodonFrames(acfList); + } + + /** + * Test mapping a column selection including hidden columns + * + * @throws IOException + */ + @Test + public void testMapColumnSelection_hiddenColumns() throws IOException + { + setupMappedAlignments(); + + ColumnSelection colsel = new ColumnSelection(); + + /* + * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3 + * in dna respectively, overall 0-4 + */ + colsel.addElement(0); + ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, + proteinView, dnaView); + assertEquals("[0, 1, 2, 3, 4]", cs.getSelected().toString()); + + fail("write me"); + } + + /** + * Test mapping a column selection in dna to its protein equivalent + * + * @throws IOException + */ + @Test + public void testMapColumnSelection_dnaToProtein() throws IOException + { + setupMappedAlignments(); + + ColumnSelection colsel = new ColumnSelection(); + + /* + * Column 0 in dna picks up first bases which map to residue 1, columns 0-1 + * in protein. + */ + colsel.addElement(0); + ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, dnaView, + proteinView); + assertEquals("[0, 1]", cs.getSelected().toString()); + + /* + * Columns 3-5 in dna map to the first residues in protein Seq1, Seq2, and + * the first two in Seq3. Overall to columns 0, 1, 3 (col2 is all gaps). + */ + colsel.addElement(3); + colsel.addElement(4); + colsel.addElement(5); + cs = MappingUtils.mapColumnSelection(colsel, dnaView, proteinView); + assertEquals("[0, 1, 3]", cs.getSelected().toString()); + } +} -- 1.7.10.2