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.util.Collection;
26 import java.util.Hashtable;
27 import java.util.LinkedHashMap;
28 import java.util.List;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
33 import javax.swing.JInternalFrame;
34 import javax.swing.JSplitPane;
35 import javax.swing.event.InternalFrameAdapter;
36 import javax.swing.event.InternalFrameEvent;
38 import fr.orsay.lri.varna.VARNAPanel;
39 import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
40 import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
41 import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
42 import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
43 import fr.orsay.lri.varna.models.BaseList;
44 import fr.orsay.lri.varna.models.FullBackup;
45 import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
46 import fr.orsay.lri.varna.models.rna.ModeleBase;
47 import fr.orsay.lri.varna.models.rna.RNA;
49 import jalview.analysis.AlignSeq;
50 import jalview.datamodel.AlignmentAnnotation;
51 import jalview.datamodel.ColumnSelection;
52 import jalview.datamodel.RnaViewerModel;
53 import jalview.datamodel.SequenceGroup;
54 import jalview.datamodel.SequenceI;
55 import jalview.ext.varna.RnaModel;
56 import jalview.structure.SecondaryStructureListener;
57 import jalview.structure.SelectionListener;
58 import jalview.structure.SelectionSource;
59 import jalview.structure.StructureSelectionManager;
60 import jalview.structure.VamsasSource;
61 import jalview.util.Comparison;
62 import jalview.util.MessageManager;
63 import jalview.util.ShiftList;
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;
175 * the annotation with the secondary structure string
177 * the AlignmentPanel creating this object
179 public AppVarna(String sname, SequenceI seq, AlignmentAnnotation aa,
184 String name = sname + " trimmed to " + seq.getName();
185 String fullName = MessageManager.formatMessage("label.varna_params",
191 * if (!aa.isValidStruc()) { throw new
192 * IllegalArgumentException("Invalid RNA structure annotation"); } final
193 * String struc = aa.getRNAStruc();
195 * String strucseq = seq.getSequenceAsString();
197 * String gappedTitle = sname + " (with gaps)"; String rnaTitle =
198 * gappedTitle; RNA gapped = new RNA(rnaTitle); try {
199 * gapped.setRNA(strucseq, replaceOddGaps(struc)); } catch
200 * (ExceptionUnmatchedClosingParentheses e2) { e2.printStackTrace(); } catch
201 * (ExceptionFileFormatOrSyntax e3) { e3.printStackTrace(); }
202 * models.put(gapped, new RnaModel(rnaTitle, aa, seq, gapped, true, null));
204 * String trimmedTitle = "trimmed " + sname; rnaTitle = trimmedTitle; RNA
205 * trimmed = trimRNA(gapped, rnaTitle); models.put(trimmed, new
206 * RnaModel(rnaTitle, aa, seq, trimmed, false, null));
208 // vab = new AppVarnaBinding(Arrays.asList(new RNA[]
209 // { trimmed, gapped }));
210 // vab = new AppVarnaBinding();
211 // // String seqName = seq.getName();
212 // // String name = sname + " trimmed to " + seqName;
215 String gappedTitle = sname + " (with gaps)";
216 RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true,
218 addModel(gappedModel, gappedTitle);
220 String trimmedTitle = "trimmed " + sname;
221 RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null,
223 addModel(trimmedModel, trimmedTitle);
224 vab.setSelectedIndex(0);
228 * Constructor that links the viewer to a parent panel (but has no structures
229 * yet - use addModel to add them)
233 protected AppVarna(AlignmentPanel ap)
236 this.viewId = System.currentTimeMillis() + "." + this.hashCode();
237 vab = new AppVarnaBinding();
240 this.ssm = ap.getStructureSelectionManager();
241 ssm.addStructureViewerListener(this);
242 ssm.addSelectionListener(this);
243 addInternalFrameListener(new InternalFrameAdapter()
246 public void internalFrameClosed(InternalFrameEvent evt)
254 * Constructor given viewer data read from a saved project file
258 * the (or a) parent alignment panel
260 public AppVarna(RnaViewerModel model, AlignmentPanel ap)
263 setTitle(model.title);
264 this.viewId = model.viewId;
265 setBounds(model.x, model.y, model.width, model.height);
266 this.split.setDividerLocation(model.dividerLocation);
270 * Constructs a split pane with an empty selection list and display panel, and
271 * adds it to the desktop
273 public void initVarna()
275 VARNAPanel varnaPanel = vab.get_varnaPanel();
276 setBackground(Color.white);
277 split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
278 vab.getListPanel(), varnaPanel);
279 getContentPane().setLayout(new BorderLayout());
280 getContentPane().add(split, BorderLayout.CENTER);
282 varnaPanel.addSelectionListener(this);
283 jalview.gui.Desktop.addInternalFrame(this, "", getBounds().width,
289 public String replaceOddGaps(String oldStr)
291 Matcher matcher = PAIRS_PATTERN.matcher(oldStr);
292 String newStr = matcher.replaceAll(".");
297 * Constructs a new RNA model from the given one, without gaps. Also
298 * calculates and saves a 'shift list'
304 public RNA trimRNA(RNA rna, String name)
306 ShiftList offset = new ShiftList();
308 RNA rnaTrim = new RNA(name);
311 rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(rna.getStructDBN()));
312 } catch (ExceptionUnmatchedClosingParentheses e2)
314 e2.printStackTrace();
315 } catch (ExceptionFileFormatOrSyntax e3)
317 e3.printStackTrace();
320 String seq = rnaTrim.getSeq();
321 StringBuilder struc = new StringBuilder(256);
322 struc.append(rnaTrim.getStructDBN());
324 int sleng = seq.length();
326 for (int i = 0; i < sleng; i++)
328 if (Comparison.isGap(seq.charAt(i)))
335 * mark base or base pair in the structure with *
337 if (!rnaTrim.findPair(i).isEmpty())
339 int m = rnaTrim.findPair(i).get(1);
340 int l = rnaTrim.findPair(i).get(0);
342 struc.replace(m, m + 1, "*");
343 struc.replace(l, l + 1, "*");
347 struc.replace(i, i + 1, "*");
354 offset.addShift(offset.shift(ofstart), ofstart - i);
362 offset.addShift(offset.shift(ofstart), ofstart - sleng);
367 * remove the marked gaps from the structure
369 String newStruc = struc.toString().replace("*", "");
372 * remove gaps from the sequence
374 String newSeq = AlignSeq.extractGaps(Comparison.GapChars, seq);
378 rnaTrim.setRNA(newSeq, newStruc);
379 registerOffset(rnaTrim, offset);
380 } catch (ExceptionUnmatchedClosingParentheses e)
383 } catch (ExceptionFileFormatOrSyntax e)
391 * Save the sequence to structure mapping, and also its inverse.
396 private void registerOffset(RNA rnaTrim, ShiftList offset)
398 offsets.put(rnaTrim, offset);
399 offsetsInv.put(rnaTrim, offset.getInverse());
402 public void showPanel(boolean show)
404 this.setVisible(show);
408 * If a mouseOver event from the AlignmentPanel is noticed the currently
409 * selected RNA in the VARNA window is highlighted at the specific position.
410 * To be able to remove it before the next highlight it is saved in
414 public void mouseOverSequence(SequenceI sequence, int index)
416 RNA rna = vab.getSelectedRNA();
421 if (models.get(rna).seq == sequence)
423 ShiftList shift = offsets.get(rna);
426 // System.err.print("Orig pos:"+index);
427 index = shift.shift(index);
428 // System.err.println("\nFinal pos:"+index);
430 mouseOverHighlighter.highlightRegion(rna, index, index);
431 vab.updateSelectedRNA(rna);
436 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
437 SelectionSource source)
441 // ignore events from anything but our parent alignpanel
442 // TODO - reuse many-one panel-view system in jmol viewer
445 RNA rna = vab.getSelectedRNA();
450 if (seqsel != null && seqsel.getSize() > 0)
452 int start = seqsel.getStartRes(), end = seqsel.getEndRes();
453 ShiftList shift = offsets.get(rna);
456 start = shift.shift(start);
457 end = shift.shift(end);
459 selectionHighlighter.highlightRegion(rna, start, end);
460 selectionHighlighter.getLastHighlight().setOutlineColor(
461 seqsel.getOutlineColour());
462 // TODO - translate column markings to positions on structure if present.
463 vab.updateSelectedRNA(rna);
467 selectionHighlighter.clearSelection();
472 * Respond to a change of the base hovered over in the Varna viewer
475 public void onHoverChanged(ModeleBase previousBase, ModeleBase newBase)
477 RNA rna = vab.getSelectedRNA();
478 ShiftList shift = offsetsInv.get(rna);
479 SequenceI seq = models.get(rna).seq;
480 if (newBase != null && seq != null)
484 int i = shift.shift(newBase.getIndex());
485 // System.err.println("shifted "+(arg1.getIndex())+" to "+i);
486 ssm.mouseOverVamsasSequence(seq, i, this);
490 ssm.mouseOverVamsasSequence(seq, newBase.getIndex(), this);
496 public void onSelectionChanged(BaseList arg0, BaseList arg1, BaseList arg2)
498 // TODO translate selected regions in VARNA to a selection on the
504 * Returns the path to a temporary file containing a representation of the
505 * state of one Varna display
511 public String getStateInfo(RNA rna)
513 return vab.getStateInfo(rna);
516 public AlignmentPanel getAlignmentPanel()
521 public String getViewId()
527 * Returns true if any of the viewer's models (not necessarily the one
528 * currently displayed) is for the given sequence
533 public boolean isListeningFor(SequenceI seq)
535 for (RnaModel model : models.values())
537 if (model.seq == seq)
546 * Returns a value representing the horizontal split divider location
550 public int getDividerLocation()
552 return split == null ? 0 : split.getDividerLocation();
556 * Tidy up as necessary when the viewer panel is closed
558 protected void close()
561 * Deregister as a listener, to release references to this object
565 ssm.removeStructureViewerListener(AppVarna.this, null);
566 ssm.removeSelectionListener(AppVarna.this);
571 * Returns the secondary structure annotation that this viewer displays for
576 public AlignmentAnnotation getAnnotation(SequenceI seq)
578 for (RnaModel model : models.values())
580 if (model.seq == seq)
588 public int getSelectedIndex()
590 return this.vab.getSelectedIndex();
594 * Returns the set of models shown by the viewer
598 public Collection<RnaModel> getModels()
600 return models.values();
604 * Add a model (e.g. loaded from project file)
609 public RNA addModel(RnaModel model, String modelName)
611 if (!model.ann.isValidStruc())
613 throw new IllegalArgumentException("Invalid RNA structure annotation");
617 * loaded from project file with Varna session data
619 if (model.varnaSession != null)
623 FullBackup fromSession = vab.vp.loadSession(model.varnaSession);
624 vab.addStructure(fromSession.rna, fromSession.config);
625 RNA rna = fromSession.rna;
626 // copy the model, but now including the RNA object
627 RnaModel newModel = new RnaModel(model.title, model.ann, model.seq,
628 rna, model.gapped, model.varnaSession);
631 registerOffset(rna, buildOffset(model.seq));
633 // TODO and add mapping (offsets)
634 models.put(rna, newModel);
635 // capture rna selection state when saved
636 selectionHighlighter = new VarnaHighlighter(rna);
637 return fromSession.rna;
638 } catch (ExceptionLoadingFailed e)
646 * opened on request in Jalview session
648 RNA rna = new RNA(modelName);
649 String struc = model.ann.getRNAStruc();
650 struc = replaceOddGaps(struc);
652 String strucseq = model.seq.getSequenceAsString();
655 rna.setRNA(strucseq, struc);
656 } catch (ExceptionUnmatchedClosingParentheses e2)
658 e2.printStackTrace();
659 } catch (ExceptionFileFormatOrSyntax e3)
661 e3.printStackTrace();
666 rna = trimRNA(rna, modelName);
668 models.put(rna, new RnaModel(modelName, model.ann, model.seq, rna,
669 model.gapped, null));
670 vab.addStructure(rna);
675 * Constructs a shift list that describes the gaps in the sequence
680 protected ShiftList buildOffset(SequenceI seq)
682 // TODO refactor to avoid duplication with trimRNA()
683 ShiftList offset = new ShiftList();
685 int sleng = seq.getLength();
686 char[] seqChars = seq.getSequence();
688 for (int i = 0; i < sleng; i++)
690 if (Comparison.isGap(seqChars[i]))
701 offset.addShift(offset.shift(ofstart), ofstart - i);
709 offset.addShift(offset.shift(ofstart), ofstart - sleng);
716 * Set the selected index in the model selection list
720 public void setSelectedIndex(int selectedRna)
722 vab.setSelectedIndex(selectedRna);