+ private static final byte[] PAIRS = new byte[] { '(', ')', '[', ']', '{',
+ '}', '<', '>' };
+
+ private AppVarnaBinding vab;
+
+ private AlignmentPanel ap;
+
+ private String viewId;
+
+ private StructureSelectionManager ssm;
+
+ /*
+ * Lookup for sequence and annotation mapped to each RNA in the viewer. Using
+ * a linked hashmap means that order is preserved when saved to the project.
+ */
+ private Map<RNA, RnaModel> models = new LinkedHashMap<RNA, RnaModel>();
+
+ private Map<RNA, ShiftList> offsets = new Hashtable<RNA, ShiftList>();
+
+ private Map<RNA, ShiftList> offsetsInv = new Hashtable<RNA, ShiftList>();
+
+ private JSplitPane split;
+
+ private VarnaHighlighter mouseOverHighlighter = new VarnaHighlighter();
+
+ private VarnaHighlighter selectionHighlighter = new VarnaHighlighter();
+
+ private class VarnaHighlighter
+ {
+ private HighlightRegionAnnotation _lastHighlight;
+
+ private RNA _lastRNAhighlighted = null;
+
+ public VarnaHighlighter()
+ {
+
+ }
+
+ /**
+ * Constructor when restoring from Varna session, including any highlight
+ * state
+ *
+ * @param rna
+ */
+ public VarnaHighlighter(RNA rna)
+ {
+ // TODO nice try but doesn't work; do we need a highlighter per model?
+ _lastRNAhighlighted = rna;
+ List<HighlightRegionAnnotation> highlights = rna.getHighlightRegion();
+ if (highlights != null && !highlights.isEmpty())
+ {
+ _lastHighlight = highlights.get(0);
+ }
+ }
+
+ /**
+ * highlight a region from start to end (inclusive) on rna
+ *
+ * @param rna
+ * @param start
+ * - first base pair index (from 0)
+ * @param end
+ * - last base pair index (from 0)
+ */
+ public void highlightRegion(RNA rna, int start, int end)
+ {
+ clearLastSelection();
+ HighlightRegionAnnotation highlight = new HighlightRegionAnnotation(
+ rna.getBasesBetween(start, end));
+ rna.addHighlightRegion(highlight);
+ _lastHighlight = highlight;
+ _lastRNAhighlighted = rna;
+ }
+
+ public HighlightRegionAnnotation getLastHighlight()
+ {
+ return _lastHighlight;
+ }
+
+ /**
+ * Clears all structure selection and refreshes the display
+ */
+ public void clearSelection()
+ {
+ if (_lastRNAhighlighted != null)
+ {
+ _lastRNAhighlighted.getHighlightRegion().clear();
+ vab.updateSelectedRNA(_lastRNAhighlighted);
+ _lastRNAhighlighted = null;
+ _lastHighlight = null;
+ }
+ }
+
+ /**
+ * Clear the last structure selection
+ */
+ public void clearLastSelection()
+ {
+ if (_lastRNAhighlighted != null)
+ {
+ _lastRNAhighlighted.removeHighlightRegion(_lastHighlight);
+ _lastRNAhighlighted = null;
+ _lastHighlight = null;
+ }
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param seq
+ * the RNA sequence
+ * @param aa
+ * the annotation with the secondary structure string
+ * @param ap
+ * the AlignmentPanel creating this object
+ */
+ public AppVarna(SequenceI seq, AlignmentAnnotation aa, AlignmentPanel ap)
+ {
+ this(ap);
+
+ String sname = aa.sequenceRef == null
+ ? "secondary structure (alignment)"
+ : seq.getName() + " structure";
+ String theTitle = sname
+ + (aa.sequenceRef == null ? " trimmed to " + seq.getName()
+ : "");
+ theTitle = MessageManager.formatMessage("label.varna_params",
+ new String[]
+ { theTitle });
+ setTitle(theTitle);
+
+ String gappedTitle = sname + " (with gaps)";
+ RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true);
+ addModel(gappedModel, gappedTitle);
+
+ String trimmedTitle = "trimmed " + sname;
+ RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null,
+ false);
+ addModel(trimmedModel, trimmedTitle);
+ vab.setSelectedIndex(0);
+ }
+
+ /**
+ * Constructor that links the viewer to a parent panel (but has no structures
+ * yet - use addModel to add them)
+ *
+ * @param ap
+ */
+ protected AppVarna(AlignmentPanel ap)
+ {
+ this.ap = ap;
+ this.viewId = System.currentTimeMillis() + "." + this.hashCode();
+ vab = new AppVarnaBinding();
+ initVarna();
+
+ this.ssm = ap.getStructureSelectionManager();
+ ssm.addStructureViewerListener(this);
+ ssm.addSelectionListener(this);
+ addInternalFrameListener(new InternalFrameAdapter()
+ {
+ @Override
+ public void internalFrameClosed(InternalFrameEvent evt)
+ {
+ close();
+ }
+ });
+ }
+
+ /**
+ * Constructor given viewer data read from a saved project file
+ *
+ * @param model
+ * @param ap
+ * the (or a) parent alignment panel
+ */
+ public AppVarna(RnaViewerModel model, AlignmentPanel ap)
+ {
+ this(ap);
+ setTitle(model.title);
+ this.viewId = model.viewId;
+ setBounds(model.x, model.y, model.width, model.height);
+ this.split.setDividerLocation(model.dividerLocation);
+ }
+
+ /**
+ * Constructs a split pane with an empty selection list and display panel, and
+ * adds it to the desktop
+ */
+ public void initVarna()
+ {
+ VARNAPanel varnaPanel = vab.get_varnaPanel();
+ setBackground(Color.white);
+ split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
+ vab.getListPanel(), varnaPanel);
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(split, BorderLayout.CENTER);
+
+ varnaPanel.addSelectionListener(this);
+ jalview.gui.Desktop.addInternalFrame(this, "", getBounds().width,
+ getBounds().height);
+ this.pack();
+ showPanel(true);
+ }
+
+ /**
+ * Constructs a new RNA model from the given one, without gaps. Also
+ * calculates and saves a 'shift list'
+ *
+ * @param rna
+ * @param name
+ * @return
+ */
+ public RNA trimRNA(RNA rna, String name)
+ {
+ ShiftList offset = new ShiftList();
+
+ RNA rnaTrim = new RNA(name);
+ try
+ {
+ String structDBN = rna.getStructDBN(true);
+ rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(structDBN));
+ } catch (ExceptionUnmatchedClosingParentheses e2)
+ {
+ e2.printStackTrace();
+ } catch (ExceptionFileFormatOrSyntax e3)
+ {
+ e3.printStackTrace();
+ }
+
+ String seq = rnaTrim.getSeq();
+ StringBuilder struc = new StringBuilder(256);
+ struc.append(rnaTrim.getStructDBN(true));
+ int ofstart = -1;
+ int sleng = seq.length();
+
+ for (int i = 0; i < sleng; i++)
+ {
+ if (Comparison.isGap(seq.charAt(i)))
+ {
+ if (ofstart == -1)
+ {
+ ofstart = i;
+ }
+ /*
+ * mark base or base & pair in the structure with *
+ */
+ if (!rnaTrim.findPair(i).isEmpty())
+ {
+ int m = rnaTrim.findPair(i).get(1);
+ int l = rnaTrim.findPair(i).get(0);
+
+ struc.replace(m, m + 1, "*");
+ struc.replace(l, l + 1, "*");
+ }
+ else
+ {
+ struc.replace(i, i + 1, "*");
+ }
+ }
+ else
+ {
+ if (ofstart > -1)
+ {
+ offset.addShift(offset.shift(ofstart), ofstart - i);
+ ofstart = -1;
+ }
+ }
+ }
+ // final gap
+ if (ofstart > -1)
+ {
+ offset.addShift(offset.shift(ofstart), ofstart - sleng);
+ ofstart = -1;
+ }
+
+ /*
+ * remove the marked gaps from the structure
+ */
+ String newStruc = struc.toString().replace("*", "");
+
+ /*
+ * remove gaps from the sequence
+ */
+ String newSeq = AlignSeq.extractGaps(Comparison.GapChars, seq);
+
+ try
+ {
+ rnaTrim.setRNA(newSeq, newStruc);
+ registerOffset(rnaTrim, offset);
+ } catch (ExceptionUnmatchedClosingParentheses e)
+ {
+ e.printStackTrace();
+ } catch (ExceptionFileFormatOrSyntax e)
+ {
+ e.printStackTrace();
+ }
+ return rnaTrim;
+ }
+
+ /**
+ * Save the sequence to structure mapping, and also its inverse.
+ *
+ * @param rnaTrim
+ * @param offset
+ */
+ private void registerOffset(RNA rnaTrim, ShiftList offset)
+ {
+ offsets.put(rnaTrim, offset);
+ offsetsInv.put(rnaTrim, offset.getInverse());
+ }