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 jalview.analysis.AlignSeq;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.ColumnSelection;
26 import jalview.datamodel.RnaViewerModel;
27 import jalview.datamodel.SequenceGroup;
28 import jalview.datamodel.SequenceI;
29 import jalview.ext.varna.RnaModel;
30 import jalview.structure.SecondaryStructureListener;
31 import jalview.structure.SelectionListener;
32 import jalview.structure.SelectionSource;
33 import jalview.structure.StructureSelectionManager;
34 import jalview.structure.VamsasSource;
35 import jalview.util.Comparison;
36 import jalview.util.MessageManager;
37 import jalview.util.ShiftList;
39 import java.awt.BorderLayout;
40 import java.awt.Color;
41 import java.util.Collection;
42 import java.util.Hashtable;
43 import java.util.LinkedHashMap;
44 import java.util.List;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
49 import javax.swing.JInternalFrame;
50 import javax.swing.JSplitPane;
51 import javax.swing.event.InternalFrameAdapter;
52 import javax.swing.event.InternalFrameEvent;
54 import fr.orsay.lri.varna.VARNAPanel;
55 import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
56 import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
57 import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
58 import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
59 import fr.orsay.lri.varna.models.BaseList;
60 import fr.orsay.lri.varna.models.FullBackup;
61 import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
62 import fr.orsay.lri.varna.models.rna.ModeleBase;
63 import fr.orsay.lri.varna.models.rna.RNA;
65 public class AppVarna extends JInternalFrame implements SelectionListener,
66 SecondaryStructureListener, InterfaceVARNASelectionListener,
69 private static final Pattern PAIRS_PATTERN = Pattern
70 .compile("[^([{<>}])]");
72 private AppVarnaBinding vab;
74 private AlignmentPanel ap;
76 private String viewId;
78 private StructureSelectionManager ssm;
81 * Lookup for sequence and annotation mapped to each RNA in the viewer. Using
82 * a linked hashmap means that order is preserved when saved to the project.
84 private Map<RNA, RnaModel> models = new LinkedHashMap<RNA, RnaModel>();
86 private Map<RNA, ShiftList> offsets = new Hashtable<RNA, ShiftList>();
88 private Map<RNA, ShiftList> offsetsInv = new Hashtable<RNA, ShiftList>();
90 private JSplitPane split;
92 private VarnaHighlighter mouseOverHighlighter = new VarnaHighlighter();
94 private VarnaHighlighter selectionHighlighter = new VarnaHighlighter();
96 private class VarnaHighlighter
98 private HighlightRegionAnnotation _lastHighlight;
100 private RNA _lastRNAhighlighted = null;
102 public VarnaHighlighter()
108 * Constructor when restoring from Varna session, including any highlight
113 public VarnaHighlighter(RNA rna)
115 // TODO nice try but doesn't work; do we need a highlighter per model?
116 _lastRNAhighlighted = rna;
117 List<HighlightRegionAnnotation> highlights = rna.getHighlightRegion();
118 if (highlights != null && !highlights.isEmpty())
120 _lastHighlight = highlights.get(0);
124 public void highlightRegion(RNA rna, int start, int end)
126 clearLastSelection();
127 HighlightRegionAnnotation highlight = new HighlightRegionAnnotation(
128 rna.getBasesBetween(start, end));
129 rna.addHighlightRegion(highlight);
130 _lastHighlight = highlight;
131 _lastRNAhighlighted = rna;
134 public HighlightRegionAnnotation getLastHighlight()
136 return _lastHighlight;
140 * Clears all structure selection and refreshes the display
142 public void clearSelection()
144 if (_lastRNAhighlighted != null)
146 _lastRNAhighlighted.getHighlightRegion().clear();
147 vab.updateSelectedRNA(_lastRNAhighlighted);
148 _lastRNAhighlighted = null;
149 _lastHighlight = null;
154 * Clear the last structure selection
156 public void clearLastSelection()
158 if (_lastRNAhighlighted != null)
160 _lastRNAhighlighted.removeHighlightRegion(_lastHighlight);
161 _lastRNAhighlighted = null;
162 _lastHighlight = null;
173 * the annotation with the secondary structure string
175 * the AlignmentPanel creating this object
177 public AppVarna(SequenceI seq, AlignmentAnnotation aa, AlignmentPanel ap)
181 String sname = aa.sequenceRef == null ? "secondary structure (alignment)"
182 : seq.getName() + " structure";
183 String theTitle = sname
184 + (aa.sequenceRef == null ? " trimmed to " + seq.getName() : "");
185 theTitle = MessageManager.formatMessage("label.varna_params",
190 String gappedTitle = sname + " (with gaps)";
191 RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true);
192 addModel(gappedModel, gappedTitle);
194 String trimmedTitle = "trimmed " + sname;
195 RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null, false);
196 addModel(trimmedModel, trimmedTitle);
197 vab.setSelectedIndex(0);
202 * Constructor that links the viewer to a parent panel (but has no structures
203 * yet - use addModel to add them)
207 protected AppVarna(AlignmentPanel ap)
210 this.viewId = System.currentTimeMillis() + "." + this.hashCode();
211 vab = new AppVarnaBinding();
214 this.ssm = ap.getStructureSelectionManager();
215 ssm.addStructureViewerListener(this);
216 ssm.addSelectionListener(this);
217 addInternalFrameListener(new InternalFrameAdapter()
220 public void internalFrameClosed(InternalFrameEvent evt)
228 * Constructor given viewer data read from a saved project file
232 * the (or a) parent alignment panel
234 public AppVarna(RnaViewerModel model, AlignmentPanel ap)
237 setTitle(model.title);
238 this.viewId = model.viewId;
239 setBounds(model.x, model.y, model.width, model.height);
240 this.split.setDividerLocation(model.dividerLocation);
244 * Constructs a split pane with an empty selection list and display panel, and
245 * adds it to the desktop
247 public void initVarna()
249 VARNAPanel varnaPanel = vab.get_varnaPanel();
250 setBackground(Color.white);
251 split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
252 vab.getListPanel(), varnaPanel);
253 getContentPane().setLayout(new BorderLayout());
254 getContentPane().add(split, BorderLayout.CENTER);
256 varnaPanel.addSelectionListener(this);
257 jalview.gui.Desktop.addInternalFrame(this, "", getBounds().width,
263 public String replaceOddGaps(String oldStr)
265 Matcher matcher = PAIRS_PATTERN.matcher(oldStr);
266 String newStr = matcher.replaceAll(".");
271 * Constructs a new RNA model from the given one, without gaps. Also
272 * calculates and saves a 'shift list'
278 public RNA trimRNA(RNA rna, String name)
280 ShiftList offset = new ShiftList();
282 RNA rnaTrim = new RNA(name);
285 rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(rna.getStructDBN()));
286 } catch (ExceptionUnmatchedClosingParentheses e2)
288 e2.printStackTrace();
289 } catch (ExceptionFileFormatOrSyntax e3)
291 e3.printStackTrace();
294 String seq = rnaTrim.getSeq();
295 StringBuilder struc = new StringBuilder(256);
296 struc.append(rnaTrim.getStructDBN());
298 int sleng = seq.length();
300 for (int i = 0; i < sleng; i++)
302 if (Comparison.isGap(seq.charAt(i)))
309 * mark base or base pair in the structure with *
311 if (!rnaTrim.findPair(i).isEmpty())
313 int m = rnaTrim.findPair(i).get(1);
314 int l = rnaTrim.findPair(i).get(0);
316 struc.replace(m, m + 1, "*");
317 struc.replace(l, l + 1, "*");
321 struc.replace(i, i + 1, "*");
328 offset.addShift(offset.shift(ofstart), ofstart - i);
336 offset.addShift(offset.shift(ofstart), ofstart - sleng);
341 * remove the marked gaps from the structure
343 String newStruc = struc.toString().replace("*", "");
346 * remove gaps from the sequence
348 String newSeq = AlignSeq.extractGaps(Comparison.GapChars, seq);
352 rnaTrim.setRNA(newSeq, newStruc);
353 registerOffset(rnaTrim, offset);
354 } catch (ExceptionUnmatchedClosingParentheses e)
357 } catch (ExceptionFileFormatOrSyntax e)
365 * Save the sequence to structure mapping, and also its inverse.
370 private void registerOffset(RNA rnaTrim, ShiftList offset)
372 offsets.put(rnaTrim, offset);
373 offsetsInv.put(rnaTrim, offset.getInverse());
376 public void showPanel(boolean show)
378 this.setVisible(show);
382 * If a mouseOver event from the AlignmentPanel is noticed the currently
383 * selected RNA in the VARNA window is highlighted at the specific position.
384 * To be able to remove it before the next highlight it is saved in
389 * the aligned sequence position (base 0)
391 * the dataset sequence position (base 1)
394 public void mouseOverSequence(SequenceI sequence, final int index,
397 RNA rna = vab.getSelectedRNA();
402 RnaModel rnaModel = models.get(rna);
403 if (rnaModel.seq == sequence)
405 int highlightPos = rnaModel.gapped ? index : position - 1;
406 mouseOverHighlighter.highlightRegion(rna, highlightPos, highlightPos);
407 vab.updateSelectedRNA(rna);
412 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
413 SelectionSource source)
417 // ignore events from anything but our parent alignpanel
418 // TODO - reuse many-one panel-view system in jmol viewer
421 RNA rna = vab.getSelectedRNA();
426 if (seqsel != null && seqsel.getSize() > 0)
428 int start = seqsel.getStartRes(), end = seqsel.getEndRes();
429 ShiftList shift = offsets.get(rna);
432 start = shift.shift(start);
433 end = shift.shift(end);
435 selectionHighlighter.highlightRegion(rna, start, end);
436 selectionHighlighter.getLastHighlight().setOutlineColor(
437 seqsel.getOutlineColour());
438 // TODO - translate column markings to positions on structure if present.
439 vab.updateSelectedRNA(rna);
443 selectionHighlighter.clearSelection();
448 * Respond to a change of the base hovered over in the Varna viewer
451 public void onHoverChanged(ModeleBase previousBase, ModeleBase newBase)
453 RNA rna = vab.getSelectedRNA();
454 ShiftList shift = offsetsInv.get(rna);
455 SequenceI seq = models.get(rna).seq;
456 if (newBase != null && seq != null)
460 int i = shift.shift(newBase.getIndex());
461 // System.err.println("shifted "+(arg1.getIndex())+" to "+i);
462 ssm.mouseOverVamsasSequence(seq, i, this);
466 ssm.mouseOverVamsasSequence(seq, newBase.getIndex(), this);
472 public void onSelectionChanged(BaseList arg0, BaseList arg1, BaseList arg2)
474 // TODO translate selected regions in VARNA to a selection on the
480 * Returns the path to a temporary file containing a representation of the
481 * state of one Varna display
487 public String getStateInfo(RNA rna)
489 return vab.getStateInfo(rna);
492 public AlignmentPanel getAlignmentPanel()
497 public String getViewId()
503 * Returns true if any of the viewer's models (not necessarily the one
504 * currently displayed) is for the given sequence
509 public boolean isListeningFor(SequenceI seq)
511 for (RnaModel model : models.values())
513 if (model.seq == seq)
522 * Returns a value representing the horizontal split divider location
526 public int getDividerLocation()
528 return split == null ? 0 : split.getDividerLocation();
532 * Tidy up as necessary when the viewer panel is closed
534 protected void close()
537 * Deregister as a listener, to release references to this object
541 ssm.removeStructureViewerListener(AppVarna.this, null);
542 ssm.removeSelectionListener(AppVarna.this);
547 * Returns the secondary structure annotation that this viewer displays for
552 public AlignmentAnnotation getAnnotation(SequenceI seq)
554 for (RnaModel model : models.values())
556 if (model.seq == seq)
564 public int getSelectedIndex()
566 return this.vab.getSelectedIndex();
570 * Returns the set of models shown by the viewer
574 public Collection<RnaModel> getModels()
576 return models.values();
580 * Add a model (e.g. loaded from project file)
585 public RNA addModel(RnaModel model, String modelName)
587 if (!model.ann.isValidStruc())
589 throw new IllegalArgumentException("Invalid RNA structure annotation");
593 * opened on request in Jalview session
595 RNA rna = new RNA(modelName);
596 String struc = model.ann.getRNAStruc();
597 struc = replaceOddGaps(struc);
599 String strucseq = model.seq.getSequenceAsString();
602 rna.setRNA(strucseq, struc);
603 } catch (ExceptionUnmatchedClosingParentheses e2)
605 e2.printStackTrace();
606 } catch (ExceptionFileFormatOrSyntax e3)
608 e3.printStackTrace();
613 rna = trimRNA(rna, modelName);
615 models.put(rna, new RnaModel(modelName, model.ann, model.seq, rna,
617 vab.addStructure(rna);
622 * Constructs a shift list that describes the gaps in the sequence
627 protected ShiftList buildOffset(SequenceI seq)
629 // TODO refactor to avoid duplication with trimRNA()
630 // TODO JAL-1789 bugs in use of ShiftList here
631 ShiftList offset = new ShiftList();
633 int sleng = seq.getLength();
634 char[] seqChars = seq.getSequence();
636 for (int i = 0; i < sleng; i++)
638 if (Comparison.isGap(seqChars[i]))
649 offset.addShift(offset.shift(ofstart), ofstart - i);
657 offset.addShift(offset.shift(ofstart), ofstart - sleng);
664 * Set the selected index in the model selection list
666 * @param selectedIndex
668 public void setInitialSelection(final int selectedIndex)
671 * empirically it needs a second for Varna/AWT to finish loading/drawing
672 * models for this to work; SwingUtilities.invokeLater _not_ a solution;
673 * explanation and/or better solution welcome!
680 } catch (InterruptedException e)
685 vab.setSelectedIndex(selectedIndex);
690 * Add a model with associated Varna session file
695 public RNA addModelSession(RnaModel model, String modelName,
698 if (!model.ann.isValidStruc())
700 throw new IllegalArgumentException("Invalid RNA structure annotation");
705 FullBackup fromSession = vab.vp.loadSession(sessionFile);
706 vab.addStructure(fromSession.rna, fromSession.config);
707 RNA rna = fromSession.rna;
708 // copy the model, but now including the RNA object
709 RnaModel newModel = new RnaModel(model.title, model.ann, model.seq,
713 registerOffset(rna, buildOffset(model.seq));
715 models.put(rna, newModel);
716 // capture rna selection state when saved
717 selectionHighlighter = new VarnaHighlighter(rna);
718 return fromSession.rna;
719 } catch (ExceptionLoadingFailed e)
722 .println("Error restoring Varna session: " + e.getMessage());