Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.gui

File AppVarna.java

 

Coverage histogram

../../img/srcFileCovDistChart1.png
52% of files have more coverage

Code metrics

80
205
32
2
782
510
85
0.41
6.41
16
2.66

Classes

Class Line # Actions
AppVarna 64 186 75
0.090909099.1%
AppVarna.VarnaHighlighter 95 19 10
0.00%
 

Contributing tests

This file is covered by 1 test. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
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.
11    *
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.
16    *
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.
20    */
21    package jalview.gui;
22   
23    import jalview.analysis.AlignSeq;
24    import jalview.datamodel.AlignmentAnnotation;
25    import jalview.datamodel.ColumnSelection;
26    import jalview.datamodel.HiddenColumns;
27    import jalview.datamodel.RnaViewerModel;
28    import jalview.datamodel.SequenceGroup;
29    import jalview.datamodel.SequenceI;
30    import jalview.ext.varna.RnaModel;
31    import jalview.structure.SecondaryStructureListener;
32    import jalview.structure.SelectionListener;
33    import jalview.structure.SelectionSource;
34    import jalview.structure.StructureSelectionManager;
35    import jalview.structure.VamsasSource;
36    import jalview.util.Comparison;
37    import jalview.util.MessageManager;
38    import jalview.util.ShiftList;
39   
40    import java.awt.BorderLayout;
41    import java.awt.Color;
42    import java.util.Collection;
43    import java.util.Hashtable;
44    import java.util.LinkedHashMap;
45    import java.util.List;
46    import java.util.Map;
47   
48    import javax.swing.JInternalFrame;
49    import javax.swing.JSplitPane;
50    import javax.swing.event.InternalFrameAdapter;
51    import javax.swing.event.InternalFrameEvent;
52   
53    import fr.orsay.lri.varna.VARNAPanel;
54    import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
55    import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
56    import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
57    import fr.orsay.lri.varna.interfaces.InterfaceVARNASelectionListener;
58    import fr.orsay.lri.varna.models.BaseList;
59    import fr.orsay.lri.varna.models.FullBackup;
60    import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
61    import fr.orsay.lri.varna.models.rna.ModeleBase;
62    import fr.orsay.lri.varna.models.rna.RNA;
63   
 
64    public class AppVarna extends JInternalFrame
65    implements SelectionListener, SecondaryStructureListener,
66    InterfaceVARNASelectionListener, VamsasSource
67    {
68    private static final byte[] PAIRS = new byte[] { '(', ')', '[', ']', '{',
69    '}', '<', '>' };
70   
71    private AppVarnaBinding vab;
72   
73    private AlignmentPanel ap;
74   
75    private String viewId;
76   
77    private StructureSelectionManager ssm;
78   
79    /*
80    * Lookup for sequence and annotation mapped to each RNA in the viewer. Using
81    * a linked hashmap means that order is preserved when saved to the project.
82    */
83    private Map<RNA, RnaModel> models = new LinkedHashMap<RNA, RnaModel>();
84   
85    private Map<RNA, ShiftList> offsets = new Hashtable<RNA, ShiftList>();
86   
87    private Map<RNA, ShiftList> offsetsInv = new Hashtable<RNA, ShiftList>();
88   
89    private JSplitPane split;
90   
91    private VarnaHighlighter mouseOverHighlighter = new VarnaHighlighter();
92   
93    private VarnaHighlighter selectionHighlighter = new VarnaHighlighter();
94   
 
95    private class VarnaHighlighter
96    {
97    private HighlightRegionAnnotation _lastHighlight;
98   
99    private RNA _lastRNAhighlighted = null;
100   
 
101  0 toggle public VarnaHighlighter()
102    {
103   
104    }
105   
106    /**
107    * Constructor when restoring from Varna session, including any highlight
108    * state
109    *
110    * @param rna
111    */
 
112  0 toggle public VarnaHighlighter(RNA rna)
113    {
114    // TODO nice try but doesn't work; do we need a highlighter per model?
115  0 _lastRNAhighlighted = rna;
116  0 List<HighlightRegionAnnotation> highlights = rna.getHighlightRegion();
117  0 if (highlights != null && !highlights.isEmpty())
118    {
119  0 _lastHighlight = highlights.get(0);
120    }
121    }
122   
123    /**
124    * highlight a region from start to end (inclusive) on rna
125    *
126    * @param rna
127    * @param start
128    * - first base pair index (from 0)
129    * @param end
130    * - last base pair index (from 0)
131    */
 
132  0 toggle public void highlightRegion(RNA rna, int start, int end)
133    {
134  0 clearLastSelection();
135  0 HighlightRegionAnnotation highlight = new HighlightRegionAnnotation(
136    rna.getBasesBetween(start, end));
137  0 rna.addHighlightRegion(highlight);
138  0 _lastHighlight = highlight;
139  0 _lastRNAhighlighted = rna;
140    }
141   
 
142  0 toggle public HighlightRegionAnnotation getLastHighlight()
143    {
144  0 return _lastHighlight;
145    }
146   
147    /**
148    * Clears all structure selection and refreshes the display
149    */
 
150  0 toggle public void clearSelection()
151    {
152  0 if (_lastRNAhighlighted != null)
153    {
154  0 _lastRNAhighlighted.getHighlightRegion().clear();
155  0 vab.updateSelectedRNA(_lastRNAhighlighted);
156  0 _lastRNAhighlighted = null;
157  0 _lastHighlight = null;
158    }
159    }
160   
161    /**
162    * Clear the last structure selection
163    */
 
164  0 toggle public void clearLastSelection()
165    {
166  0 if (_lastRNAhighlighted != null)
167    {
168  0 _lastRNAhighlighted.removeHighlightRegion(_lastHighlight);
169  0 _lastRNAhighlighted = null;
170  0 _lastHighlight = null;
171    }
172    }
173    }
174   
175    /**
176    * Constructor
177    *
178    * @param seq
179    * the RNA sequence
180    * @param aa
181    * the annotation with the secondary structure string
182    * @param ap
183    * the AlignmentPanel creating this object
184    */
 
185  0 toggle public AppVarna(SequenceI seq, AlignmentAnnotation aa, AlignmentPanel ap)
186    {
187  0 this(ap);
188   
189  0 String sname = aa.sequenceRef == null
190    ? "secondary structure (alignment)"
191    : seq.getName() + " structure";
192  0 String theTitle = sname
193  0 + (aa.sequenceRef == null ? " trimmed to " + seq.getName()
194    : "");
195  0 theTitle = MessageManager.formatMessage("label.varna_params",
196    new String[]
197    { theTitle });
198  0 setTitle(theTitle);
199   
200  0 String gappedTitle = sname + " (with gaps)";
201  0 RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true);
202  0 addModel(gappedModel, gappedTitle);
203   
204  0 String trimmedTitle = "trimmed " + sname;
205  0 RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null,
206    false);
207  0 addModel(trimmedModel, trimmedTitle);
208  0 vab.setSelectedIndex(0);
209    }
210   
211    /**
212    * Constructor that links the viewer to a parent panel (but has no structures
213    * yet - use addModel to add them)
214    *
215    * @param ap
216    */
 
217  0 toggle protected AppVarna(AlignmentPanel ap)
218    {
219  0 this.ap = ap;
220  0 this.viewId = System.currentTimeMillis() + "." + this.hashCode();
221  0 vab = new AppVarnaBinding();
222  0 initVarna();
223   
224  0 this.ssm = ap.getStructureSelectionManager();
225  0 ssm.addStructureViewerListener(this);
226  0 ssm.addSelectionListener(this);
227  0 addInternalFrameListener(new InternalFrameAdapter()
228    {
 
229  0 toggle @Override
230    public void internalFrameClosed(InternalFrameEvent evt)
231    {
232  0 close();
233    }
234    });
235    }
236   
237    /**
238    * Constructor given viewer data read from a saved project file
239    *
240    * @param model
241    * @param ap
242    * the (or a) parent alignment panel
243    */
 
244  0 toggle public AppVarna(RnaViewerModel model, AlignmentPanel ap)
245    {
246  0 this(ap);
247  0 setTitle(model.title);
248  0 this.viewId = model.viewId;
249  0 setBounds(model.x, model.y, model.width, model.height);
250  0 this.split.setDividerLocation(model.dividerLocation);
251    }
252   
253    /**
254    * Constructs a split pane with an empty selection list and display panel, and
255    * adds it to the desktop
256    */
 
257  0 toggle public void initVarna()
258    {
259  0 VARNAPanel varnaPanel = vab.get_varnaPanel();
260  0 setBackground(Color.white);
261  0 split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
262    vab.getListPanel(), varnaPanel);
263  0 getContentPane().setLayout(new BorderLayout());
264  0 getContentPane().add(split, BorderLayout.CENTER);
265   
266  0 varnaPanel.addSelectionListener(this);
267  0 jalview.gui.Desktop.addInternalFrame(this, "", getBounds().width,
268    getBounds().height);
269  0 this.pack();
270  0 showPanel(true);
271    }
272   
273    /**
274    * Constructs a new RNA model from the given one, without gaps. Also
275    * calculates and saves a 'shift list'
276    *
277    * @param rna
278    * @param name
279    * @return
280    */
 
281  0 toggle public RNA trimRNA(RNA rna, String name)
282    {
283  0 ShiftList offset = new ShiftList();
284   
285  0 RNA rnaTrim = new RNA(name);
286  0 try
287    {
288  0 String structDBN = rna.getStructDBN(true);
289  0 rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(structDBN));
290    } catch (ExceptionUnmatchedClosingParentheses e2)
291    {
292  0 e2.printStackTrace();
293    } catch (ExceptionFileFormatOrSyntax e3)
294    {
295  0 e3.printStackTrace();
296    }
297   
298  0 String seq = rnaTrim.getSeq();
299  0 StringBuilder struc = new StringBuilder(256);
300  0 struc.append(rnaTrim.getStructDBN(true));
301  0 int ofstart = -1;
302  0 int sleng = seq.length();
303   
304  0 for (int i = 0; i < sleng; i++)
305    {
306  0 if (Comparison.isGap(seq.charAt(i)))
307    {
308  0 if (ofstart == -1)
309    {
310  0 ofstart = i;
311    }
312    /*
313    * mark base or base & pair in the structure with *
314    */
315  0 if (!rnaTrim.findPair(i).isEmpty())
316    {
317  0 int m = rnaTrim.findPair(i).get(1);
318  0 int l = rnaTrim.findPair(i).get(0);
319   
320  0 struc.replace(m, m + 1, "*");
321  0 struc.replace(l, l + 1, "*");
322    }
323    else
324    {
325  0 struc.replace(i, i + 1, "*");
326    }
327    }
328    else
329    {
330  0 if (ofstart > -1)
331    {
332  0 offset.addShift(offset.shift(ofstart), ofstart - i);
333  0 ofstart = -1;
334    }
335    }
336    }
337    // final gap
338  0 if (ofstart > -1)
339    {
340  0 offset.addShift(offset.shift(ofstart), ofstart - sleng);
341  0 ofstart = -1;
342    }
343   
344    /*
345    * remove the marked gaps from the structure
346    */
347  0 String newStruc = struc.toString().replace("*", "");
348   
349    /*
350    * remove gaps from the sequence
351    */
352  0 String newSeq = AlignSeq.extractGaps(Comparison.GapChars, seq);
353   
354  0 try
355    {
356  0 rnaTrim.setRNA(newSeq, newStruc);
357  0 registerOffset(rnaTrim, offset);
358    } catch (ExceptionUnmatchedClosingParentheses e)
359    {
360  0 e.printStackTrace();
361    } catch (ExceptionFileFormatOrSyntax e)
362    {
363  0 e.printStackTrace();
364    }
365  0 return rnaTrim;
366    }
367   
368    /**
369    * Save the sequence to structure mapping, and also its inverse.
370    *
371    * @param rnaTrim
372    * @param offset
373    */
 
374  0 toggle private void registerOffset(RNA rnaTrim, ShiftList offset)
375    {
376  0 offsets.put(rnaTrim, offset);
377  0 offsetsInv.put(rnaTrim, offset.getInverse());
378    }
379   
 
380  0 toggle public void showPanel(boolean show)
381    {
382  0 this.setVisible(show);
383    }
384   
385    /**
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
389    * _lastHighlight
390    *
391    * @param sequence
392    * @param index
393    * the aligned sequence position (base 0)
394    * @param position
395    * the dataset sequence position (base 1)
396    */
 
397  0 toggle @Override
398    public void mouseOverSequence(SequenceI sequence, final int index,
399    final int position)
400    {
401  0 RNA rna = vab.getSelectedRNA();
402  0 if (rna == null)
403    {
404  0 return;
405    }
406  0 RnaModel rnaModel = models.get(rna);
407  0 if (rnaModel.seq == sequence)
408    {
409  0 int highlightPos = rnaModel.gapped ? index
410    : position - sequence.getStart();
411  0 mouseOverHighlighter.highlightRegion(rna, highlightPos, highlightPos);
412  0 vab.updateSelectedRNA(rna);
413    }
414    }
415   
 
416  0 toggle @Override
417    public void selection(SequenceGroup seqsel, ColumnSelection colsel,
418    HiddenColumns hidden, SelectionSource source)
419    {
420  0 if (source != ap.av)
421    {
422    // ignore events from anything but our parent alignpanel
423    // TODO - reuse many-one panel-view system in jmol viewer
424  0 return;
425    }
426  0 RNA rna = vab.getSelectedRNA();
427  0 if (rna == null)
428    {
429  0 return;
430    }
431   
432  0 RnaModel rnaModel = models.get(rna);
433   
434  0 if (seqsel != null && seqsel.getSize() > 0
435    && seqsel.contains(rnaModel.seq))
436    {
437  0 int start = seqsel.getStartRes(), end = seqsel.getEndRes();
438  0 if (rnaModel.gapped)
439    {
440  0 ShiftList shift = offsets.get(rna);
441  0 if (shift != null)
442    {
443  0 start = shift.shift(start);
444  0 end = shift.shift(end);
445    }
446    }
447    else
448    {
449  0 start = rnaModel.seq.findPosition(start) - rnaModel.seq.getStart();
450  0 end = rnaModel.seq.findPosition(end) - rnaModel.seq.getStart();
451    }
452   
453  0 selectionHighlighter.highlightRegion(rna, start, end);
454  0 selectionHighlighter.getLastHighlight()
455    .setOutlineColor(seqsel.getOutlineColour());
456    // TODO - translate column markings to positions on structure if present.
457  0 vab.updateSelectedRNA(rna);
458    }
459    else
460    {
461  0 selectionHighlighter.clearSelection();
462    }
463    }
464   
465    /**
466    * Respond to a change of the base hovered over in the Varna viewer
467    */
 
468  0 toggle @Override
469    public void onHoverChanged(ModeleBase previousBase, ModeleBase newBase)
470    {
471  0 RNA rna = vab.getSelectedRNA();
472  0 ShiftList shift = offsetsInv.get(rna);
473  0 SequenceI seq = models.get(rna).seq;
474  0 if (newBase != null && seq != null)
475    {
476  0 if (shift != null)
477    {
478  0 int i = shift.shift(newBase.getIndex());
479    // System.err.println("shifted "+(arg1.getIndex())+" to "+i);
480  0 ssm.mouseOverVamsasSequence(seq, i, this);
481    }
482    else
483    {
484  0 ssm.mouseOverVamsasSequence(seq, newBase.getIndex(), this);
485    }
486    }
487    }
488   
 
489  0 toggle @Override
490    public void onSelectionChanged(BaseList arg0, BaseList arg1,
491    BaseList arg2)
492    {
493    // TODO translate selected regions in VARNA to a selection on the
494    // alignpanel.
495   
496    }
497   
498    /**
499    * Returns the path to a temporary file containing a representation of the
500    * state of one Varna display
501    *
502    * @param rna
503    *
504    * @return
505    */
 
506  0 toggle public String getStateInfo(RNA rna)
507    {
508  0 return vab.getStateInfo(rna);
509    }
510   
 
511  0 toggle public AlignmentPanel getAlignmentPanel()
512    {
513  0 return ap;
514    }
515   
 
516  0 toggle public String getViewId()
517    {
518  0 return viewId;
519    }
520   
521    /**
522    * Returns true if any of the viewer's models (not necessarily the one
523    * currently displayed) is for the given sequence
524    *
525    * @param seq
526    * @return
527    */
 
528  0 toggle public boolean isListeningFor(SequenceI seq)
529    {
530  0 for (RnaModel model : models.values())
531    {
532  0 if (model.seq == seq)
533    {
534  0 return true;
535    }
536    }
537  0 return false;
538    }
539   
540    /**
541    * Returns a value representing the horizontal split divider location
542    *
543    * @return
544    */
 
545  0 toggle public int getDividerLocation()
546    {
547  0 return split == null ? 0 : split.getDividerLocation();
548    }
549   
550    /**
551    * Tidy up as necessary when the viewer panel is closed
552    */
 
553  0 toggle protected void close()
554    {
555    /*
556    * Deregister as a listener, to release references to this object
557    */
558  0 if (ssm != null)
559    {
560  0 ssm.removeStructureViewerListener(AppVarna.this, null);
561  0 ssm.removeSelectionListener(AppVarna.this);
562    }
563    }
564   
565    /**
566    * Returns the secondary structure annotation that this viewer displays for
567    * the given sequence
568    *
569    * @return
570    */
 
571  0 toggle public AlignmentAnnotation getAnnotation(SequenceI seq)
572    {
573  0 for (RnaModel model : models.values())
574    {
575  0 if (model.seq == seq)
576    {
577  0 return model.ann;
578    }
579    }
580  0 return null;
581    }
582   
 
583  0 toggle public int getSelectedIndex()
584    {
585  0 return this.vab.getSelectedIndex();
586    }
587   
588    /**
589    * Returns the set of models shown by the viewer
590    *
591    * @return
592    */
 
593  0 toggle public Collection<RnaModel> getModels()
594    {
595  0 return models.values();
596    }
597   
598    /**
599    * Add a model (e.g. loaded from project file)
600    *
601    * @param rna
602    * @param modelName
603    */
 
604  0 toggle public RNA addModel(RnaModel model, String modelName)
605    {
606  0 if (!model.ann.isValidStruc())
607    {
608  0 throw new IllegalArgumentException(
609    "Invalid RNA structure annotation");
610    }
611   
612    /*
613    * opened on request in Jalview session
614    */
615  0 RNA rna = new RNA(modelName);
616  0 String struc = model.ann.getRNAStruc();
617  0 struc = replaceOddGaps(struc);
618   
619  0 String strucseq = model.seq.getSequenceAsString();
620  0 try
621    {
622  0 rna.setRNA(strucseq, struc);
623    } catch (ExceptionUnmatchedClosingParentheses e2)
624    {
625  0 e2.printStackTrace();
626    } catch (ExceptionFileFormatOrSyntax e3)
627    {
628  0 e3.printStackTrace();
629    }
630   
631  0 if (!model.gapped)
632    {
633  0 rna = trimRNA(rna, modelName);
634    }
635  0 models.put(rna, new RnaModel(modelName, model.ann, model.seq, rna,
636    model.gapped));
637  0 vab.addStructure(rna);
638  0 return rna;
639    }
640   
641    /**
642    * Constructs a shift list that describes the gaps in the sequence
643    *
644    * @param seq
645    * @return
646    */
 
647  0 toggle protected ShiftList buildOffset(SequenceI seq)
648    {
649    // TODO refactor to avoid duplication with trimRNA()
650    // TODO JAL-1789 bugs in use of ShiftList here
651  0 ShiftList offset = new ShiftList();
652  0 int ofstart = -1;
653  0 int sleng = seq.getLength();
654   
655  0 for (int i = 0; i < sleng; i++)
656    {
657  0 if (Comparison.isGap(seq.getCharAt(i)))
658    {
659  0 if (ofstart == -1)
660    {
661  0 ofstart = i;
662    }
663    }
664    else
665    {
666  0 if (ofstart > -1)
667    {
668  0 offset.addShift(offset.shift(ofstart), ofstart - i);
669  0 ofstart = -1;
670    }
671    }
672    }
673    // final gap
674  0 if (ofstart > -1)
675    {
676  0 offset.addShift(offset.shift(ofstart), ofstart - sleng);
677  0 ofstart = -1;
678    }
679  0 return offset;
680    }
681   
682    /**
683    * Set the selected index in the model selection list
684    *
685    * @param selectedIndex
686    */
 
687  0 toggle public void setInitialSelection(final int selectedIndex)
688    {
689    /*
690    * empirically it needs a second for Varna/AWT to finish loading/drawing
691    * models for this to work; SwingUtilities.invokeLater _not_ a solution;
692    * explanation and/or better solution welcome!
693    */
694  0 synchronized (this)
695    {
696  0 try
697    {
698  0 wait(1000);
699    } catch (InterruptedException e)
700    {
701    // meh
702    }
703    }
704  0 vab.setSelectedIndex(selectedIndex);
705    }
706   
707    /**
708    * Add a model with associated Varna session file
709    *
710    * @param rna
711    * @param modelName
712    */
 
713  0 toggle public RNA addModelSession(RnaModel model, String modelName,
714    String sessionFile)
715    {
716  0 if (!model.ann.isValidStruc())
717    {
718  0 throw new IllegalArgumentException(
719    "Invalid RNA structure annotation");
720    }
721   
722  0 try
723    {
724  0 FullBackup fromSession = vab.vp.loadSession(sessionFile);
725  0 vab.addStructure(fromSession.rna, fromSession.config);
726  0 RNA rna = fromSession.rna;
727    // copy the model, but now including the RNA object
728  0 RnaModel newModel = new RnaModel(model.title, model.ann, model.seq,
729    rna, model.gapped);
730  0 if (!model.gapped)
731    {
732  0 registerOffset(rna, buildOffset(model.seq));
733    }
734  0 models.put(rna, newModel);
735    // capture rna selection state when saved
736  0 selectionHighlighter = new VarnaHighlighter(rna);
737  0 return fromSession.rna;
738    } catch (ExceptionLoadingFailed e)
739    {
740  0 System.err
741    .println("Error restoring Varna session: " + e.getMessage());
742  0 return null;
743    }
744    }
745   
746    /**
747    * Replace everything except RNA secondary structure characters with a period
748    *
749    * @param s
750    * @return
751    */
 
752  5 toggle public static String replaceOddGaps(String s)
753    {
754  5 if (s == null)
755    {
756  1 return null;
757    }
758   
759    // this is measured to be 10 times faster than a regex replace
760  4 boolean changed = false;
761  4 byte[] bytes = s.getBytes();
762  30 for (int i = 0; i < bytes.length; i++)
763    {
764  26 boolean ok = false;
765    // todo check for ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) if
766    // wanted also
767  183 for (int j = 0; !ok && (j < PAIRS.length); j++)
768    {
769  157 if (bytes[i] == PAIRS[j])
770    {
771  15 ok = true;
772    }
773    }
774  26 if (!ok)
775    {
776  11 bytes[i] = '.';
777  11 changed = true;
778    }
779    }
780  4 return changed ? new String(bytes) : s;
781    }
782    }