2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.lang.reflect.InvocationTargetException;
26 import java.util.Collection;
27 import java.util.Hashtable;
28 import java.util.LinkedHashMap;
29 import java.util.List;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
34 import javax.swing.JInternalFrame;
35 import javax.swing.JSplitPane;
36 import javax.swing.event.InternalFrameAdapter;
37 import javax.swing.event.InternalFrameEvent;
39 import fr.orsay.lri.varna.VARNAPanel;
40 import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
41 import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
42 import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
43 import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
44 import fr.orsay.lri.varna.models.BaseList;
45 import fr.orsay.lri.varna.models.FullBackup;
46 import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
47 import fr.orsay.lri.varna.models.rna.ModeleBase;
48 import fr.orsay.lri.varna.models.rna.RNA;
50 import jalview.analysis.AlignSeq;
51 import jalview.datamodel.AlignmentAnnotation;
52 import jalview.datamodel.ColumnSelection;
53 import jalview.datamodel.RnaViewerModel;
54 import jalview.datamodel.SequenceGroup;
55 import jalview.datamodel.SequenceI;
56 import jalview.ext.varna.RnaModel;
57 import jalview.structure.SecondaryStructureListener;
58 import jalview.structure.SelectionListener;
59 import jalview.structure.SelectionSource;
60 import jalview.structure.StructureSelectionManager;
61 import jalview.structure.VamsasSource;
62 import jalview.util.Comparison;
63 import jalview.util.MessageManager;
64 import jalview.util.ShiftList;
66 public class AppVarna extends JInternalFrame implements SelectionListener,
67 SecondaryStructureListener, InterfaceVARNASelectionListener,
70 private static final Pattern PAIRS_PATTERN = Pattern
71 .compile("[^([{<>}])]");
73 private AppVarnaBinding vab;
75 private AlignmentPanel ap;
77 private String viewId;
79 private StructureSelectionManager ssm;
82 * Lookup for sequence and annotation mapped to each RNA in the viewer. Using
83 * a linked hashmap means that order is preserved when saved to the project.
85 private Map<RNA, RnaModel> models = new LinkedHashMap<RNA, RnaModel>();
87 private Map<RNA, ShiftList> offsets = new Hashtable<RNA, ShiftList>();
89 private Map<RNA, ShiftList> offsetsInv = new Hashtable<RNA, ShiftList>();
91 private JSplitPane split;
93 private VarnaHighlighter mouseOverHighlighter = new VarnaHighlighter();
95 private VarnaHighlighter selectionHighlighter = new VarnaHighlighter();
97 private class VarnaHighlighter
99 private HighlightRegionAnnotation _lastHighlight;
101 private RNA _lastRNAhighlighted = null;
103 public VarnaHighlighter()
109 * Constructor when restoring from Varna session, including any highlight
114 public VarnaHighlighter(RNA rna)
116 // TODO nice try but doesn't work; do we need a highlighter per model?
117 _lastRNAhighlighted = rna;
118 List<HighlightRegionAnnotation> highlights = rna.getHighlightRegion();
119 if (highlights != null && !highlights.isEmpty())
121 _lastHighlight = highlights.get(0);
125 public void highlightRegion(RNA rna, int start, int end)
127 clearLastSelection();
128 HighlightRegionAnnotation highlight = new HighlightRegionAnnotation(
129 rna.getBasesBetween(start, end));
130 rna.addHighlightRegion(highlight);
131 _lastHighlight = highlight;
132 _lastRNAhighlighted = rna;
135 public HighlightRegionAnnotation getLastHighlight()
137 return _lastHighlight;
141 * Clears all structure selection and refreshes the display
143 public void clearSelection()
145 if (_lastRNAhighlighted != null)
147 _lastRNAhighlighted.getHighlightRegion().clear();
148 vab.updateSelectedRNA(_lastRNAhighlighted);
149 _lastRNAhighlighted = null;
150 _lastHighlight = null;
155 * Clear the last structure selection
157 public void clearLastSelection()
159 if (_lastRNAhighlighted != null)
161 _lastRNAhighlighted.removeHighlightRegion(_lastHighlight);
162 _lastRNAhighlighted = null;
163 _lastHighlight = null;
174 * the annotation with the secondary structure string
176 * the AlignmentPanel creating this object
178 public AppVarna(SequenceI seq, AlignmentAnnotation aa, AlignmentPanel ap)
182 String sname = aa.sequenceRef == null ? "secondary structure (alignment)"
183 : seq.getName() + " structure";
184 String theTitle = sname
185 + (aa.sequenceRef == null ? " trimmed to " + seq.getName() : "");
186 theTitle = MessageManager.formatMessage("label.varna_params",
191 String gappedTitle = sname + " (with gaps)";
192 RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true,
194 addModel(gappedModel, gappedTitle);
196 String trimmedTitle = "trimmed " + sname;
197 RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null,
199 addModel(trimmedModel, trimmedTitle);
200 vab.setSelectedIndex(0);
204 * Constructor that links the viewer to a parent panel (but has no structures
205 * yet - use addModel to add them)
209 protected AppVarna(AlignmentPanel ap)
212 this.viewId = System.currentTimeMillis() + "." + this.hashCode();
213 vab = new AppVarnaBinding();
216 this.ssm = ap.getStructureSelectionManager();
217 ssm.addStructureViewerListener(this);
218 ssm.addSelectionListener(this);
219 addInternalFrameListener(new InternalFrameAdapter()
222 public void internalFrameClosed(InternalFrameEvent evt)
230 * Constructor given viewer data read from a saved project file
234 * the (or a) parent alignment panel
236 public AppVarna(RnaViewerModel model, AlignmentPanel ap)
239 setTitle(model.title);
240 this.viewId = model.viewId;
241 setBounds(model.x, model.y, model.width, model.height);
242 this.split.setDividerLocation(model.dividerLocation);
246 * Constructs a split pane with an empty selection list and display panel, and
247 * adds it to the desktop
249 public void initVarna()
251 VARNAPanel varnaPanel = vab.get_varnaPanel();
252 setBackground(Color.white);
253 split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
254 vab.getListPanel(), varnaPanel);
255 getContentPane().setLayout(new BorderLayout());
256 getContentPane().add(split, BorderLayout.CENTER);
258 varnaPanel.addSelectionListener(this);
259 jalview.gui.Desktop.addInternalFrame(this, "", getBounds().width,
265 public String replaceOddGaps(String oldStr)
267 Matcher matcher = PAIRS_PATTERN.matcher(oldStr);
268 String newStr = matcher.replaceAll(".");
273 * Constructs a new RNA model from the given one, without gaps. Also
274 * calculates and saves a 'shift list'
280 public RNA trimRNA(RNA rna, String name)
282 ShiftList offset = new ShiftList();
284 RNA rnaTrim = new RNA(name);
287 rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(rna.getStructDBN()));
288 } catch (ExceptionUnmatchedClosingParentheses e2)
290 e2.printStackTrace();
291 } catch (ExceptionFileFormatOrSyntax e3)
293 e3.printStackTrace();
296 String seq = rnaTrim.getSeq();
297 StringBuilder struc = new StringBuilder(256);
298 struc.append(rnaTrim.getStructDBN());
300 int sleng = seq.length();
302 for (int i = 0; i < sleng; i++)
304 if (Comparison.isGap(seq.charAt(i)))
311 * mark base or base pair in the structure with *
313 if (!rnaTrim.findPair(i).isEmpty())
315 int m = rnaTrim.findPair(i).get(1);
316 int l = rnaTrim.findPair(i).get(0);
318 struc.replace(m, m + 1, "*");
319 struc.replace(l, l + 1, "*");
323 struc.replace(i, i + 1, "*");
330 offset.addShift(offset.shift(ofstart), ofstart - i);
338 offset.addShift(offset.shift(ofstart), ofstart - sleng);
343 * remove the marked gaps from the structure
345 String newStruc = struc.toString().replace("*", "");
348 * remove gaps from the sequence
350 String newSeq = AlignSeq.extractGaps(Comparison.GapChars, seq);
354 rnaTrim.setRNA(newSeq, newStruc);
355 registerOffset(rnaTrim, offset);
356 } catch (ExceptionUnmatchedClosingParentheses e)
359 } catch (ExceptionFileFormatOrSyntax e)
367 * Save the sequence to structure mapping, and also its inverse.
372 private void registerOffset(RNA rnaTrim, ShiftList offset)
374 offsets.put(rnaTrim, offset);
375 offsetsInv.put(rnaTrim, offset.getInverse());
378 public void showPanel(boolean show)
380 this.setVisible(show);
384 * If a mouseOver event from the AlignmentPanel is noticed the currently
385 * selected RNA in the VARNA window is highlighted at the specific position.
386 * To be able to remove it before the next highlight it is saved in
391 * the aligned sequence position (base 0)
393 * the dataset sequence position (base 1)
396 public void mouseOverSequence(SequenceI sequence, final int index,
399 RNA rna = vab.getSelectedRNA();
404 RnaModel rnaModel = models.get(rna);
405 if (rnaModel.seq == sequence)
407 int highlightPos = rnaModel.gapped ? index : position - 1;
408 // int highlightPos = index;
409 // ShiftList shift = offsets.get(rna);
410 // if (shift != null)
412 // System.err.print("Orig pos:" + index);
413 // highlightPos = shift.shift(index);
414 // System.err.println("\nFinal pos:" + index);
416 mouseOverHighlighter.highlightRegion(rna, highlightPos, highlightPos);
417 vab.updateSelectedRNA(rna);
422 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
423 SelectionSource source)
427 // ignore events from anything but our parent alignpanel
428 // TODO - reuse many-one panel-view system in jmol viewer
431 RNA rna = vab.getSelectedRNA();
436 if (seqsel != null && seqsel.getSize() > 0)
438 int start = seqsel.getStartRes(), end = seqsel.getEndRes();
439 ShiftList shift = offsets.get(rna);
442 start = shift.shift(start);
443 end = shift.shift(end);
445 selectionHighlighter.highlightRegion(rna, start, end);
446 selectionHighlighter.getLastHighlight().setOutlineColor(
447 seqsel.getOutlineColour());
448 // TODO - translate column markings to positions on structure if present.
449 vab.updateSelectedRNA(rna);
453 selectionHighlighter.clearSelection();
458 * Respond to a change of the base hovered over in the Varna viewer
461 public void onHoverChanged(ModeleBase previousBase, ModeleBase newBase)
463 RNA rna = vab.getSelectedRNA();
464 ShiftList shift = offsetsInv.get(rna);
465 SequenceI seq = models.get(rna).seq;
466 if (newBase != null && seq != null)
470 int i = shift.shift(newBase.getIndex());
471 // System.err.println("shifted "+(arg1.getIndex())+" to "+i);
472 ssm.mouseOverVamsasSequence(seq, i, this);
476 ssm.mouseOverVamsasSequence(seq, newBase.getIndex(), this);
482 public void onSelectionChanged(BaseList arg0, BaseList arg1, BaseList arg2)
484 // TODO translate selected regions in VARNA to a selection on the
490 * Returns the path to a temporary file containing a representation of the
491 * state of one Varna display
497 public String getStateInfo(RNA rna)
499 return vab.getStateInfo(rna);
502 public AlignmentPanel getAlignmentPanel()
507 public String getViewId()
513 * Returns true if any of the viewer's models (not necessarily the one
514 * currently displayed) is for the given sequence
519 public boolean isListeningFor(SequenceI seq)
521 for (RnaModel model : models.values())
523 if (model.seq == seq)
532 * Returns a value representing the horizontal split divider location
536 public int getDividerLocation()
538 return split == null ? 0 : split.getDividerLocation();
542 * Tidy up as necessary when the viewer panel is closed
544 protected void close()
547 * Deregister as a listener, to release references to this object
551 ssm.removeStructureViewerListener(AppVarna.this, null);
552 ssm.removeSelectionListener(AppVarna.this);
557 * Returns the secondary structure annotation that this viewer displays for
562 public AlignmentAnnotation getAnnotation(SequenceI seq)
564 for (RnaModel model : models.values())
566 if (model.seq == seq)
574 public int getSelectedIndex()
576 return this.vab.getSelectedIndex();
580 * Returns the set of models shown by the viewer
584 public Collection<RnaModel> getModels()
586 return models.values();
590 * Add a model (e.g. loaded from project file)
595 public RNA addModel(RnaModel model, String modelName)
597 if (!model.ann.isValidStruc())
599 throw new IllegalArgumentException("Invalid RNA structure annotation");
603 * loaded from project file with Varna session data
605 if (model.varnaSession != null)
609 FullBackup fromSession = vab.vp.loadSession(model.varnaSession);
610 vab.addStructure(fromSession.rna, fromSession.config);
611 RNA rna = fromSession.rna;
612 // copy the model, but now including the RNA object
613 RnaModel newModel = new RnaModel(model.title, model.ann, model.seq,
614 rna, model.gapped, model.varnaSession);
617 registerOffset(rna, buildOffset(model.seq));
619 models.put(rna, newModel);
620 // capture rna selection state when saved
621 selectionHighlighter = new VarnaHighlighter(rna);
622 return fromSession.rna;
623 } catch (ExceptionLoadingFailed e)
631 * opened on request in Jalview session
633 RNA rna = new RNA(modelName);
634 String struc = model.ann.getRNAStruc();
635 struc = replaceOddGaps(struc);
637 String strucseq = model.seq.getSequenceAsString();
640 rna.setRNA(strucseq, struc);
641 } catch (ExceptionUnmatchedClosingParentheses e2)
643 e2.printStackTrace();
644 } catch (ExceptionFileFormatOrSyntax e3)
646 e3.printStackTrace();
651 rna = trimRNA(rna, modelName);
653 models.put(rna, new RnaModel(modelName, model.ann, model.seq, rna,
654 model.gapped, null));
655 vab.addStructure(rna);
660 * Constructs a shift list that describes the gaps in the sequence
665 protected ShiftList buildOffset(SequenceI seq)
667 // TODO refactor to avoid duplication with trimRNA()
668 // TODO JAL-1789 bugs in use of ShiftList here
669 ShiftList offset = new ShiftList();
671 int sleng = seq.getLength();
672 char[] seqChars = seq.getSequence();
674 for (int i = 0; i < sleng; i++)
676 if (Comparison.isGap(seqChars[i]))
687 offset.addShift(offset.shift(ofstart), ofstart - i);
695 offset.addShift(offset.shift(ofstart), ofstart - sleng);
702 * Set the selected index in the model selection list
706 public void setInitialSelection(final int selectedIndex)
708 vab.setSelectedIndex(selectedIndex);