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;
31 import javax.swing.JInternalFrame;
32 import javax.swing.JSplitPane;
33 import javax.swing.event.InternalFrameAdapter;
34 import javax.swing.event.InternalFrameEvent;
36 import fr.orsay.lri.varna.VARNAPanel;
37 import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
38 import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
39 import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
40 import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
41 import fr.orsay.lri.varna.models.BaseList;
42 import fr.orsay.lri.varna.models.FullBackup;
43 import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
44 import fr.orsay.lri.varna.models.rna.ModeleBase;
45 import fr.orsay.lri.varna.models.rna.RNA;
46 import jalview.analysis.AlignSeq;
47 import jalview.datamodel.AlignmentAnnotation;
48 import jalview.datamodel.ColumnSelection;
49 import jalview.datamodel.HiddenColumns;
50 import jalview.datamodel.RnaViewerModel;
51 import jalview.datamodel.SequenceGroup;
52 import jalview.datamodel.SequenceI;
53 import jalview.ext.varna.RnaModel;
54 import jalview.structure.SecondaryStructureListener;
55 import jalview.structure.SelectionListener;
56 import jalview.structure.SelectionSource;
57 import jalview.structure.StructureSelectionManager;
58 import jalview.structure.VamsasSource;
59 import jalview.util.Comparison;
60 import jalview.util.MessageManager;
61 import jalview.util.ShiftList;
63 public class AppVarna extends JInternalFrame
64 implements SelectionListener, SecondaryStructureListener,
65 InterfaceVARNASelectionListener, VamsasSource
67 private static final byte[] PAIRS = new byte[] { '(', ')', '[', ']', '{',
70 private AppVarnaBinding vab;
72 private AlignmentPanel ap;
74 private String viewId;
76 private StructureSelectionManager ssm;
79 * Lookup for sequence and annotation mapped to each RNA in the viewer. Using
80 * a linked hashmap means that order is preserved when saved to the project.
82 private Map<RNA, RnaModel> models = new LinkedHashMap<RNA, RnaModel>();
84 private Map<RNA, ShiftList> offsets = new Hashtable<RNA, ShiftList>();
86 private Map<RNA, ShiftList> offsetsInv = new Hashtable<RNA, ShiftList>();
88 private JSplitPane split;
90 private VarnaHighlighter mouseOverHighlighter = new VarnaHighlighter();
92 private VarnaHighlighter selectionHighlighter = new VarnaHighlighter();
94 private class VarnaHighlighter
96 private HighlightRegionAnnotation _lastHighlight;
98 private RNA _lastRNAhighlighted = null;
100 public VarnaHighlighter()
106 * Constructor when restoring from Varna session, including any highlight
111 public VarnaHighlighter(RNA rna)
113 // TODO nice try but doesn't work; do we need a highlighter per model?
114 _lastRNAhighlighted = rna;
115 List<HighlightRegionAnnotation> highlights = rna.getHighlightRegion();
116 if (highlights != null && !highlights.isEmpty())
118 _lastHighlight = highlights.get(0);
123 * highlight a region from start to end (inclusive) on rna
127 * - first base pair index (from 0)
129 * - last base pair index (from 0)
131 public void highlightRegion(RNA rna, int start, int end)
133 clearLastSelection();
134 HighlightRegionAnnotation highlight = new HighlightRegionAnnotation(
135 rna.getBasesBetween(start, end));
136 rna.addHighlightRegion(highlight);
137 _lastHighlight = highlight;
138 _lastRNAhighlighted = rna;
141 public HighlightRegionAnnotation getLastHighlight()
143 return _lastHighlight;
147 * Clears all structure selection and refreshes the display
149 public void clearSelection()
151 if (_lastRNAhighlighted != null)
153 _lastRNAhighlighted.getHighlightRegion().clear();
154 vab.updateSelectedRNA(_lastRNAhighlighted);
155 _lastRNAhighlighted = null;
156 _lastHighlight = null;
161 * Clear the last structure selection
163 public void clearLastSelection()
165 if (_lastRNAhighlighted != null)
167 _lastRNAhighlighted.removeHighlightRegion(_lastHighlight);
168 _lastRNAhighlighted = null;
169 _lastHighlight = null;
180 * the annotation with the secondary structure string
182 * the AlignmentPanel creating this object
184 public AppVarna(SequenceI seq, AlignmentAnnotation aa, AlignmentPanel ap)
188 String sname = aa.sequenceRef == null
189 ? "secondary structure (alignment)"
190 : seq.getName() + " structure";
191 String theTitle = sname
192 + (aa.sequenceRef == null ? " trimmed to " + seq.getName()
194 theTitle = MessageManager.formatMessage("label.varna_params",
199 String gappedTitle = sname + " (with gaps)";
200 RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true);
201 addModel(gappedModel, gappedTitle);
203 String trimmedTitle = "trimmed " + sname;
204 RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null,
206 addModel(trimmedModel, trimmedTitle);
207 vab.setSelectedIndex(0);
211 * Constructor that links the viewer to a parent panel (but has no structures
212 * yet - use addModel to add them)
216 protected AppVarna(AlignmentPanel ap)
218 this.setFrameIcon(null);
220 this.viewId = System.currentTimeMillis() + "." + this.hashCode();
221 vab = new AppVarnaBinding();
224 this.ssm = ap.getStructureSelectionManager();
225 ssm.addStructureViewerListener(this);
226 ssm.addSelectionListener(this);
227 addInternalFrameListener(new InternalFrameAdapter()
230 public void internalFrameClosed(InternalFrameEvent evt)
238 * Constructor given viewer data read from a saved project file
242 * the (or a) parent alignment panel
244 public AppVarna(RnaViewerModel model, AlignmentPanel ap)
247 setTitle(model.title);
248 this.viewId = model.viewId;
249 setBounds(model.x, model.y, model.width, model.height);
250 this.split.setDividerLocation(model.dividerLocation);
254 * Constructs a split pane with an empty selection list and display panel, and
255 * adds it to the desktop
257 public void initVarna()
259 VARNAPanel varnaPanel = vab.get_varnaPanel();
260 setBackground(Color.white);
261 split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
262 vab.getListPanel(), varnaPanel);
263 getContentPane().setLayout(new BorderLayout());
264 getContentPane().add(split, BorderLayout.CENTER);
266 varnaPanel.addSelectionListener(this);
267 jalview.gui.Desktop.addInternalFrame(this, "", getBounds().width,
274 * Constructs a new RNA model from the given one, without gaps. Also
275 * calculates and saves a 'shift list'
281 public RNA trimRNA(RNA rna, String name)
283 ShiftList offset = new ShiftList();
285 RNA rnaTrim = new RNA(name);
288 String structDBN = rna.getStructDBN(true);
289 rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(structDBN));
290 } catch (ExceptionUnmatchedClosingParentheses e2)
292 e2.printStackTrace();
293 } catch (ExceptionFileFormatOrSyntax e3)
295 e3.printStackTrace();
298 String seq = rnaTrim.getSeq();
299 StringBuilder struc = new StringBuilder(256);
300 struc.append(rnaTrim.getStructDBN(true));
302 int sleng = seq.length();
304 for (int i = 0; i < sleng; i++)
306 if (Comparison.isGap(seq.charAt(i)))
313 * mark base or base & pair in the structure with *
315 if (!rnaTrim.findPair(i).isEmpty())
317 int m = rnaTrim.findPair(i).get(1);
318 int l = rnaTrim.findPair(i).get(0);
320 struc.replace(m, m + 1, "*");
321 struc.replace(l, l + 1, "*");
325 struc.replace(i, i + 1, "*");
332 offset.addShift(offset.shift(ofstart), ofstart - i);
340 offset.addShift(offset.shift(ofstart), ofstart - sleng);
345 * remove the marked gaps from the structure
347 String newStruc = struc.toString().replace("*", "");
350 * remove gaps from the sequence
352 String newSeq = AlignSeq.extractGaps(Comparison.GapChars, seq);
356 rnaTrim.setRNA(newSeq, newStruc);
357 registerOffset(rnaTrim, offset);
358 } catch (ExceptionUnmatchedClosingParentheses e)
361 } catch (ExceptionFileFormatOrSyntax e)
369 * Save the sequence to structure mapping, and also its inverse.
374 private void registerOffset(RNA rnaTrim, ShiftList offset)
376 offsets.put(rnaTrim, offset);
377 offsetsInv.put(rnaTrim, offset.getInverse());
380 public void showPanel(boolean show)
382 this.setVisible(show);
386 * If a mouseOver event from the AlignmentPanel is noticed the currently
387 * selected RNA in the VARNA window is highlighted at the specific position.
388 * To be able to remove it before the next highlight it is saved in
393 * the aligned sequence position (base 0)
395 * the dataset sequence position (base 1)
398 public void mouseOverSequence(SequenceI sequence, final int index,
401 RNA rna = vab.getSelectedRNA();
406 RnaModel rnaModel = models.get(rna);
407 if (rnaModel.seq == sequence)
409 int highlightPos = rnaModel.gapped ? index
410 : position - sequence.getStart();
411 mouseOverHighlighter.highlightRegion(rna, highlightPos, highlightPos);
412 vab.updateSelectedRNA(rna);
417 public void selection(SequenceGroup seqsel, ColumnSelection colsel,
418 HiddenColumns hidden, SelectionSource source)
422 // ignore events from anything but our parent alignpanel
423 // TODO - reuse many-one panel-view system in jmol viewer
426 RNA rna = vab.getSelectedRNA();
432 RnaModel rnaModel = models.get(rna);
434 if (seqsel != null && seqsel.getSize() > 0
435 && seqsel.contains(rnaModel.seq))
437 int start = seqsel.getStartRes(), end = seqsel.getEndRes();
440 ShiftList shift = offsets.get(rna);
443 start = shift.shift(start);
444 end = shift.shift(end);
449 start = rnaModel.seq.findPosition(start) - rnaModel.seq.getStart();
450 end = rnaModel.seq.findPosition(end) - rnaModel.seq.getStart();
453 selectionHighlighter.highlightRegion(rna, start, end);
454 selectionHighlighter.getLastHighlight()
455 .setOutlineColor(seqsel.getOutlineColour());
456 // TODO - translate column markings to positions on structure if present.
457 vab.updateSelectedRNA(rna);
461 selectionHighlighter.clearSelection();
466 * Respond to a change of the base hovered over in the Varna viewer
469 public void onHoverChanged(ModeleBase previousBase, ModeleBase newBase)
471 RNA rna = vab.getSelectedRNA();
472 ShiftList shift = offsetsInv.get(rna);
473 SequenceI seq = models.get(rna).seq;
474 if (newBase != null && seq != null)
478 int i = shift.shift(newBase.getIndex());
479 // jalview.bin.Console.errPrintln("shifted "+(arg1.getIndex())+" to
481 ssm.mouseOverVamsasSequence(seq, i, this);
485 ssm.mouseOverVamsasSequence(seq, newBase.getIndex(), this);
491 public void onSelectionChanged(BaseList arg0, BaseList arg1,
494 // TODO translate selected regions in VARNA to a selection on the
500 * Returns the path to a temporary file containing a representation of the
501 * state of one Varna display
507 public String getStateInfo(RNA rna)
509 return vab.getStateInfo(rna);
512 public AlignmentPanel getAlignmentPanel()
517 public String getViewId()
523 * Returns true if any of the viewer's models (not necessarily the one
524 * currently displayed) is for the given sequence
529 public boolean isListeningFor(SequenceI seq)
531 for (RnaModel model : models.values())
533 if (model.seq == seq)
542 * Returns a value representing the horizontal split divider location
546 public int getDividerLocation()
548 return split == null ? 0 : split.getDividerLocation();
552 * Tidy up as necessary when the viewer panel is closed
554 protected void close()
557 * Deregister as a listener, to release references to this object
561 ssm.removeStructureViewerListener(AppVarna.this, null);
562 ssm.removeSelectionListener(AppVarna.this);
567 * Returns the secondary structure annotation that this viewer displays for
572 public AlignmentAnnotation getAnnotation(SequenceI seq)
574 for (RnaModel model : models.values())
576 if (model.seq == seq)
584 public int getSelectedIndex()
586 return this.vab.getSelectedIndex();
590 * Returns the set of models shown by the viewer
594 public Collection<RnaModel> getModels()
596 return models.values();
600 * Add a model (e.g. loaded from project file)
605 public RNA addModel(RnaModel model, String modelName)
607 if (!model.ann.isValidStruc())
609 throw new IllegalArgumentException(
610 "Invalid RNA structure annotation");
614 * opened on request in Jalview session
616 RNA rna = new RNA(modelName);
617 String struc = model.ann.getRNAStruc();
618 struc = replaceOddGaps(struc);
620 String strucseq = model.seq.getSequenceAsString();
623 rna.setRNA(strucseq, struc);
624 } catch (ExceptionUnmatchedClosingParentheses e2)
626 e2.printStackTrace();
627 } catch (ExceptionFileFormatOrSyntax e3)
629 e3.printStackTrace();
634 rna = trimRNA(rna, modelName);
636 models.put(rna, new RnaModel(modelName, model.ann, model.seq, rna,
638 vab.addStructure(rna);
643 * Constructs a shift list that describes the gaps in the sequence
648 protected ShiftList buildOffset(SequenceI seq)
650 // TODO refactor to avoid duplication with trimRNA()
651 // TODO JAL-1789 bugs in use of ShiftList here
652 ShiftList offset = new ShiftList();
654 int sleng = seq.getLength();
656 for (int i = 0; i < sleng; i++)
658 if (Comparison.isGap(seq.getCharAt(i)))
669 offset.addShift(offset.shift(ofstart), ofstart - i);
677 offset.addShift(offset.shift(ofstart), ofstart - sleng);
684 * Set the selected index in the model selection list
686 * @param selectedIndex
688 public void setInitialSelection(final int selectedIndex)
691 * empirically it needs a second for Varna/AWT to finish loading/drawing
692 * models for this to work; SwingUtilities.invokeLater _not_ a solution;
693 * explanation and/or better solution welcome!
700 } catch (InterruptedException e)
705 vab.setSelectedIndex(selectedIndex);
709 * Add a model with associated Varna session file
714 public RNA addModelSession(RnaModel model, String modelName,
717 if (!model.ann.isValidStruc())
719 throw new IllegalArgumentException(
720 "Invalid RNA structure annotation");
725 FullBackup fromSession = vab.vp.loadSession(sessionFile);
726 vab.addStructure(fromSession.rna, fromSession.config);
727 RNA rna = fromSession.rna;
728 // copy the model, but now including the RNA object
729 RnaModel newModel = new RnaModel(model.title, model.ann, model.seq,
733 registerOffset(rna, buildOffset(model.seq));
735 models.put(rna, newModel);
736 // capture rna selection state when saved
737 selectionHighlighter = new VarnaHighlighter(rna);
738 return fromSession.rna;
739 } catch (ExceptionLoadingFailed e)
742 .println("Error restoring Varna session: " + e.getMessage());
748 * Replace everything except RNA secondary structure characters with a period
753 public static String replaceOddGaps(String s)
760 // this is measured to be 10 times faster than a regex replace
761 boolean changed = false;
762 byte[] bytes = s.getBytes();
763 for (int i = 0; i < bytes.length; i++)
766 // todo check for ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) if
768 for (int j = 0; !ok && (j < PAIRS.length); j++)
770 if (bytes[i] == PAIRS[j])
781 return changed ? new String(bytes) : s;