Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.appletgui

File AnnotationPanel.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
60% of files have more coverage

Code metrics

142
266
25
1
802
618
113
0.42
10.64
25
4.52

Classes

Class Line # Actions
AnnotationPanel 65 266 113
0.00%
 

Contributing tests

No tests hitting this source file were found.

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.appletgui;
22   
23    import jalview.datamodel.AlignmentAnnotation;
24    import jalview.datamodel.Annotation;
25    import jalview.datamodel.SequenceI;
26    import jalview.renderer.AnnotationRenderer;
27    import jalview.renderer.AwtRenderPanelI;
28    import jalview.schemes.ResidueProperties;
29    import jalview.util.Comparison;
30    import jalview.util.MessageManager;
31    import jalview.util.Platform;
32    import jalview.viewmodel.ViewportListenerI;
33    import jalview.viewmodel.ViewportRanges;
34   
35    import java.awt.Color;
36    import java.awt.Dimension;
37    import java.awt.Font;
38    import java.awt.FontMetrics;
39    import java.awt.Graphics;
40    import java.awt.Image;
41    import java.awt.MenuItem;
42    import java.awt.Panel;
43    import java.awt.PopupMenu;
44    import java.awt.event.ActionEvent;
45    import java.awt.event.ActionListener;
46    import java.awt.event.AdjustmentEvent;
47    import java.awt.event.AdjustmentListener;
48    import java.awt.event.InputEvent;
49    import java.awt.event.MouseEvent;
50    import java.awt.event.MouseListener;
51    import java.awt.event.MouseMotionListener;
52    import java.beans.PropertyChangeEvent;
53   
54    import jalview.datamodel.AlignmentAnnotation;
55    import jalview.datamodel.Annotation;
56    import jalview.datamodel.SequenceI;
57    import jalview.renderer.AnnotationRenderer;
58    import jalview.renderer.AwtRenderPanelI;
59    import jalview.schemes.ResidueProperties;
60    import jalview.util.Comparison;
61    import jalview.util.MessageManager;
62    import jalview.viewmodel.ViewportListenerI;
63    import jalview.viewmodel.ViewportRanges;
64   
 
65    public class AnnotationPanel extends Panel
66    implements AwtRenderPanelI, AdjustmentListener, ActionListener,
67    MouseListener, MouseMotionListener, ViewportListenerI
68    {
69    AlignViewport av;
70   
71    AlignmentPanel ap;
72   
73    int activeRow = -1;
74   
75    final String HELIX = "Helix";
76   
77    final String SHEET = "Sheet";
78   
79    /**
80    * For RNA secondary structure "stems" aka helices
81    */
82    final String STEM = "RNA Helix";
83   
84    final String LABEL = "Label";
85   
86    final String REMOVE = "Remove Annotation";
87   
88    final String COLOUR = "Colour";
89   
90    final Color HELIX_COLOUR = Color.red.darker();
91   
92    final Color SHEET_COLOUR = Color.green.darker().darker();
93   
94    Image image;
95   
96    Graphics gg;
97   
98    FontMetrics fm;
99   
100    int imgWidth = 0;
101   
102    boolean fastPaint = false;
103   
104    // Used For mouse Dragging and resizing graphs
105    int graphStretch = -1;
106   
107    int graphStretchY = -1;
108   
109    boolean mouseDragging = false;
110   
111    public static int GRAPH_HEIGHT = 40;
112   
113    // boolean MAC = false;
114   
115    public final AnnotationRenderer renderer;
116   
 
117  0 toggle public AnnotationPanel(AlignmentPanel ap)
118    {
119  0 new jalview.util.Platform();
120    // MAC = Platform.isAMac();
121  0 this.ap = ap;
122  0 av = ap.av;
123  0 setLayout(null);
124  0 int height = adjustPanelHeight();
125  0 ap.apvscroll.setValues(0, getSize().height, 0, height);
126   
127  0 addMouseMotionListener(this);
128   
129  0 addMouseListener(this);
130   
131    // ap.annotationScroller.getVAdjustable().addAdjustmentListener( this );
132  0 renderer = new AnnotationRenderer();
133   
134  0 av.getRanges().addPropertyChangeListener(this);
135    }
136   
 
137  0 toggle public AnnotationPanel(AlignViewport av)
138    {
139  0 this.av = av;
140  0 renderer = new AnnotationRenderer();
141   
142    }
143   
 
144  0 toggle @Override
145    public void adjustmentValueChanged(AdjustmentEvent evt)
146    {
147    }
148   
149    /**
150    * DOCUMENT ME!
151    *
152    * @param evt
153    * DOCUMENT ME!
154    */
 
155  0 toggle @Override
156    public void actionPerformed(ActionEvent evt)
157    {
158  0 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
159  0 if (aa == null)
160    {
161  0 return;
162    }
163  0 Annotation[] anot = aa[activeRow].annotations;
164   
165  0 if (anot.length < av.getColumnSelection().getMax())
166    {
167  0 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
168    + 2];
169  0 System.arraycopy(anot, 0, temp, 0, anot.length);
170  0 anot = temp;
171  0 aa[activeRow].annotations = anot;
172    }
173   
174  0 String label = "";
175  0 if (av.getColumnSelection() != null
176    && !av.getColumnSelection().isEmpty()
177    && anot[av.getColumnSelection().getMin()] != null)
178    {
179  0 label = anot[av.getColumnSelection().getMin()].displayCharacter;
180    }
181   
182  0 if (evt.getActionCommand().equals(REMOVE))
183    {
184  0 for (int index : av.getColumnSelection().getSelected())
185    {
186  0 if (av.getAlignment().getHiddenColumns().isVisible(index))
187    {
188  0 anot[index] = null;
189    }
190    }
191    }
192  0 else if (evt.getActionCommand().equals(LABEL))
193    {
194  0 label = enterLabel(label, "Enter Label");
195   
196  0 if (label == null)
197    {
198  0 return;
199    }
200   
201  0 if ((label.length() > 0) && !aa[activeRow].hasText)
202    {
203  0 aa[activeRow].hasText = true;
204    }
205   
206  0 for (int index : av.getColumnSelection().getSelected())
207    {
208    // TODO: JAL-2001 - provide a fast method to list visible selected
209    // columns
210  0 if (!av.getAlignment().getHiddenColumns().isVisible(index))
211    {
212  0 continue;
213    }
214   
215  0 if (anot[index] == null)
216    {
217  0 anot[index] = new Annotation(label, "", ' ', 0);
218    }
219   
220  0 anot[index].displayCharacter = label;
221    }
222    }
223  0 else if (evt.getActionCommand().equals(COLOUR))
224    {
225  0 UserDefinedColours udc = new UserDefinedColours(this, Color.black,
226    ap.alignFrame);
227   
228  0 Color col = udc.getColor();
229   
230  0 for (int index : av.getColumnSelection().getSelected())
231    {
232  0 if (!av.getAlignment().getHiddenColumns().isVisible(index))
233    {
234  0 continue;
235    }
236   
237  0 if (anot[index] == null)
238    {
239  0 anot[index] = new Annotation("", "", ' ', 0);
240    }
241   
242  0 anot[index].colour = col;
243    }
244    }
245    else
246    // HELIX OR SHEET
247    {
248  0 char type = 0;
249  0 String symbol = "\u03B1";
250   
251  0 if (evt.getActionCommand().equals(HELIX))
252    {
253  0 type = 'H';
254    }
255  0 else if (evt.getActionCommand().equals(SHEET))
256    {
257  0 type = 'E';
258  0 symbol = "\u03B2";
259    }
260   
261    // Added by LML to color stems
262  0 else if (evt.getActionCommand().equals(STEM))
263    {
264  0 type = 'S';
265  0 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
266  0 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
267    }
268   
269  0 if (!aa[activeRow].hasIcons)
270    {
271  0 aa[activeRow].hasIcons = true;
272    }
273   
274  0 label = enterLabel(symbol, "Enter Label");
275   
276  0 if (label == null)
277    {
278  0 return;
279    }
280   
281  0 if ((label.length() > 0) && !aa[activeRow].hasText)
282    {
283  0 aa[activeRow].hasText = true;
284  0 if (evt.getActionCommand().equals(STEM))
285    {
286  0 aa[activeRow].showAllColLabels = true;
287    }
288    }
289   
290  0 for (int index : av.getColumnSelection().getSelected())
291    {
292  0 if (!av.getAlignment().getHiddenColumns().isVisible(index))
293    {
294  0 continue;
295    }
296   
297  0 if (anot[index] == null)
298    {
299  0 anot[index] = new Annotation(label, "", type, 0);
300    }
301   
302  0 anot[index].secondaryStructure = type != 'S' ? type
303  0 : label.length() == 0 ? ' ' : label.charAt(0);
304  0 anot[index].displayCharacter = label;
305    }
306    }
307   
308  0 av.getAlignment().validateAnnotation(aa[activeRow]);
309   
310  0 ap.alignmentChanged();
311  0 adjustPanelHeight();
312  0 repaint();
313   
314  0 return;
315    }
316   
 
317  0 toggle String enterLabel(String text, String label)
318    {
319  0 EditNameDialog dialog = new EditNameDialog(text, null, label, null,
320    ap.alignFrame, "Enter Label", 400, 200, true);
321   
322  0 if (dialog.accept)
323    {
324  0 return dialog.getName();
325    }
326    else
327    {
328  0 return null;
329    }
330    }
331   
 
332  0 toggle @Override
333    public void mousePressed(MouseEvent evt)
334    {
335  0 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
336  0 if (aa == null)
337    {
338  0 return;
339    }
340   
341  0 int height = -scrollOffset;
342  0 activeRow = -1;
343   
344  0 for (int i = 0; i < aa.length; i++)
345    {
346  0 if (aa[i].visible)
347    {
348  0 height += aa[i].height;
349    }
350   
351  0 if (evt.getY() < height)
352    {
353  0 if (aa[i].editable)
354    {
355  0 activeRow = i;
356    }
357  0 else if (aa[i].graph != AlignmentAnnotation.NO_GRAPH)
358    {
359    // Stretch Graph
360  0 graphStretch = i;
361  0 graphStretchY = evt.getY();
362    }
363   
364  0 break;
365    }
366    }
367   
368  0 if ((evt.getModifiersEx()
369    & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK
370    && activeRow != -1)
371    {
372  0 if (av.getColumnSelection() == null
373    || av.getColumnSelection().isEmpty())
374    {
375  0 return;
376    }
377   
378  0 PopupMenu pop = new PopupMenu(
379    MessageManager.getString("label.structure_type"));
380  0 MenuItem item;
381   
382  0 if (av.getAlignment().isNucleotide())
383    {
384  0 item = new MenuItem(STEM);
385  0 item.addActionListener(this);
386  0 pop.add(item);
387    }
388    else
389    {
390  0 item = new MenuItem(HELIX);
391  0 item.addActionListener(this);
392  0 pop.add(item);
393  0 item = new MenuItem(SHEET);
394  0 item.addActionListener(this);
395  0 pop.add(item);
396    }
397  0 item = new MenuItem(LABEL);
398  0 item.addActionListener(this);
399  0 pop.add(item);
400  0 item = new MenuItem(COLOUR);
401  0 item.addActionListener(this);
402  0 pop.add(item);
403  0 item = new MenuItem(REMOVE);
404  0 item.addActionListener(this);
405  0 pop.add(item);
406  0 ap.alignFrame.add(pop);
407  0 pop.show(this, evt.getX(), evt.getY());
408   
409  0 return;
410    }
411   
412  0 ap.scalePanel.mousePressed(evt);
413    }
414   
 
415  0 toggle @Override
416    public void mouseReleased(MouseEvent evt)
417    {
418  0 graphStretch = -1;
419  0 graphStretchY = -1;
420  0 mouseDragging = false;
421  0 if (needValidating)
422    {
423  0 ap.validate();
424  0 needValidating = false;
425    }
426  0 ap.scalePanel.mouseReleased(evt);
427    }
428   
 
429  0 toggle @Override
430    public void mouseClicked(MouseEvent evt)
431    {
432    }
433   
434    boolean needValidating = false;
435   
 
436  0 toggle @Override
437    public void mouseDragged(MouseEvent evt)
438    {
439  0 if (graphStretch > -1)
440    {
441  0 av.getAlignment()
442    .getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
443    - evt.getY();
444  0 if (av.getAlignment()
445    .getAlignmentAnnotation()[graphStretch].graphHeight < 0)
446    {
447  0 av.getAlignment()
448    .getAlignmentAnnotation()[graphStretch].graphHeight = 0;
449    }
450  0 graphStretchY = evt.getY();
451  0 av.calcPanelHeight();
452  0 needValidating = true;
453    // TODO: only update overview visible geometry
454  0 ap.paintAlignment(true, false);
455    }
456    else
457    {
458  0 ap.scalePanel.mouseDragged(evt);
459    }
460    }
461   
 
462  0 toggle @Override
463    public void mouseMoved(MouseEvent evt)
464    {
465  0 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
466  0 if (aa == null)
467    {
468  0 return;
469    }
470   
471  0 int row = -1;
472  0 int height = -scrollOffset;
473  0 for (int i = 0; i < aa.length; i++)
474    {
475   
476  0 if (aa[i].visible)
477    {
478  0 height += aa[i].height;
479    }
480   
481  0 if (evt.getY() < height)
482    {
483  0 row = i;
484  0 break;
485    }
486    }
487   
488  0 int column = evt.getX() / av.getCharWidth()
489    + av.getRanges().getStartRes();
490   
491  0 if (av.hasHiddenColumns())
492    {
493  0 column = av.getAlignment().getHiddenColumns()
494    .visibleToAbsoluteColumn(column);
495    }
496   
497  0 if (row > -1 && column < aa[row].annotations.length
498    && aa[row].annotations[column] != null)
499    {
500  0 StringBuilder text = new StringBuilder();
501  0 text.append(MessageManager.getString("label.column")).append(" ")
502    .append(column + 1);
503  0 String description = aa[row].annotations[column].description;
504  0 if (description != null && description.length() > 0)
505    {
506  0 text.append(" ").append(description);
507    }
508   
509    /*
510    * if the annotation is sequence-specific, show the sequence number
511    * in the alignment, and (if not a gap) the residue and position
512    */
513  0 SequenceI seqref = aa[row].sequenceRef;
514  0 if (seqref != null)
515    {
516  0 int seqIndex = av.getAlignment().findIndex(seqref);
517  0 if (seqIndex != -1)
518    {
519  0 text.append(", ")
520    .append(MessageManager.getString("label.sequence"))
521    .append(" ").append(seqIndex + 1);
522  0 char residue = seqref.getCharAt(column);
523  0 if (!Comparison.isGap(residue))
524    {
525  0 text.append(" ");
526  0 String name;
527  0 if (av.getAlignment().isNucleotide())
528    {
529  0 name = ResidueProperties.nucleotideName
530    .get(String.valueOf(residue));
531  0 text.append(" Nucleotide: ")
532  0 .append(name != null ? name : residue);
533    }
534    else
535    {
536  0 name = 'X' == residue ? "X"
537  0 : ('*' == residue ? "STOP"
538    : ResidueProperties.aa2Triplet
539    .get(String.valueOf(residue)));
540  0 text.append(" Residue: ")
541  0 .append(name != null ? name : residue);
542    }
543  0 int residuePos = seqref.findPosition(column);
544  0 text.append(" (").append(residuePos).append(")");
545    // int residuePos = seqref.findPosition(column);
546    // text.append(residue).append(" (")
547    // .append(residuePos).append(")");
548    }
549    }
550    }
551   
552  0 ap.alignFrame.statusBar.setText(text.toString());
553    }
554    }
555   
 
556  0 toggle @Override
557    public void mouseEntered(MouseEvent evt)
558    {
559  0 ap.scalePanel.mouseEntered(evt);
560    }
561   
 
562  0 toggle @Override
563    public void mouseExited(MouseEvent evt)
564    {
565  0 ap.scalePanel.mouseExited(evt);
566    }
567   
 
568  0 toggle public int adjustPanelHeight()
569    {
570  0 return adjustPanelHeight(true);
571    }
572   
 
573  0 toggle public int adjustPanelHeight(boolean repaint)
574    {
575  0 int height = av.calcPanelHeight();
576  0 this.setSize(new Dimension(getSize().width, height));
577  0 if (repaint)
578    {
579  0 repaint();
580    }
581  0 return height;
582    }
583   
584    /**
585    * calculate the height for visible annotation, revalidating bounds where
586    * necessary ABSTRACT GUI METHOD
587    *
588    * @return total height of annotation
589    */
590   
 
591  0 toggle public void addEditableColumn(int i)
592    {
593  0 if (activeRow == -1)
594    {
595  0 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
596  0 if (aa == null)
597    {
598  0 return;
599    }
600   
601  0 for (int j = 0; j < aa.length; j++)
602    {
603  0 if (aa[j].editable)
604    {
605  0 activeRow = j;
606  0 break;
607    }
608    }
609    }
610    }
611   
 
612  0 toggle @Override
613    public void update(Graphics g)
614    {
615  0 paint(g);
616    }
617   
 
618  0 toggle @Override
619    public void paint(Graphics g)
620    {
621  0 Dimension d = getSize();
622  0 imgWidth = d.width;
623    // (av.endRes - av.startRes + 1) * av.charWidth;
624  0 if (imgWidth < 1 || d.height < 1)
625    {
626  0 return;
627    }
628  0 if (image == null || imgWidth != image.getWidth(this)
629    || d.height != image.getHeight(this))
630    {
631  0 image = createImage(imgWidth, d.height);
632  0 gg = image.getGraphics();
633  0 gg.setFont(av.getFont());
634  0 fm = gg.getFontMetrics();
635  0 fastPaint = false;
636    }
637   
638  0 if (fastPaint)
639    {
640  0 g.drawImage(image, 0, 0, this);
641  0 fastPaint = false;
642  0 return;
643    }
644   
645  0 gg.setColor(Color.white);
646  0 gg.fillRect(0, 0, getSize().width, getSize().height);
647  0 drawComponent(gg, av.getRanges().getStartRes(),
648    av.getRanges().getEndRes() + 1);
649   
650  0 g.drawImage(image, 0, 0, this);
651    }
652   
 
653  0 toggle public void fastPaint(int horizontal)
654    {
655  0 if (horizontal == 0 || gg == null
656    || av.getAlignment().getAlignmentAnnotation() == null
657    || av.getAlignment().getAlignmentAnnotation().length < 1)
658    {
659  0 repaint();
660  0 return;
661    }
662   
663  0 gg.copyArea(0, 0, imgWidth, getSize().height,
664    -horizontal * av.getCharWidth(), 0);
665  0 int sr = av.getRanges().getStartRes(),
666    er = av.getRanges().getEndRes() + 1, transX = 0;
667   
668  0 if (horizontal > 0) // scrollbar pulled right, image to the left
669    {
670  0 transX = (er - sr - horizontal) * av.getCharWidth();
671  0 sr = er - horizontal;
672    }
673  0 else if (horizontal < 0)
674    {
675  0 er = sr - horizontal;
676    }
677   
678  0 gg.translate(transX, 0);
679   
680  0 drawComponent(gg, sr, er);
681   
682  0 gg.translate(-transX, 0);
683   
684  0 fastPaint = true;
685  0 repaint();
686    }
687   
688    /**
689    * DOCUMENT ME!
690    *
691    * @param g
692    * DOCUMENT ME!
693    * @param startRes
694    * DOCUMENT ME!
695    * @param endRes
696    * DOCUMENT ME!
697    */
 
698  0 toggle public void drawComponent(Graphics g, int startRes, int endRes)
699    {
700  0 Font ofont = av.getFont();
701  0 g.setFont(ofont);
702   
703  0 g.setColor(Color.white);
704  0 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(),
705    getSize().height);
706   
707  0 if (fm == null)
708    {
709  0 fm = g.getFontMetrics();
710    }
711   
712  0 if ((av.getAlignment().getAlignmentAnnotation() == null)
713    || (av.getAlignment().getAlignmentAnnotation().length < 1))
714    {
715  0 g.setColor(Color.white);
716  0 g.fillRect(0, 0, getSize().width, getSize().height);
717  0 g.setColor(Color.black);
718  0 if (av.validCharWidth)
719    {
720  0 g.drawString(MessageManager
721    .getString("label.alignment_has_no_annotations"), 20, 15);
722    }
723   
724  0 return;
725    }
726  0 g.translate(0, -scrollOffset);
727  0 renderer.drawComponent(this, av, g, activeRow, startRes, endRes);
728  0 g.translate(0, +scrollOffset);
729    }
730   
731    int scrollOffset = 0;
732   
 
733  0 toggle public void setScrollOffset(int value, boolean repaint)
734    {
735  0 scrollOffset = value;
736  0 if (repaint)
737    {
738  0 repaint();
739    }
740    }
741   
 
742  0 toggle @Override
743    public FontMetrics getFontMetrics()
744    {
745  0 return fm;
746    }
747   
 
748  0 toggle @Override
749    public Image getFadedImage()
750    {
751  0 return image;
752    }
753   
 
754  0 toggle @Override
755    public int getFadedImageWidth()
756    {
757  0 return imgWidth;
758    }
759   
760    private int[] bounds = new int[2];
761   
 
762  0 toggle @Override
763    public int[] getVisibleVRange()
764    {
765  0 if (ap != null && ap.alabels != null)
766    {
767  0 int sOffset = -ap.alabels.scrollOffset;
768  0 int visHeight = sOffset + ap.annotationPanelHolder.getHeight();
769  0 bounds[0] = sOffset;
770  0 bounds[1] = visHeight;
771  0 return bounds;
772    }
773    else
774    {
775  0 return null;
776    }
777    }
778   
 
779  0 toggle @Override
780    public void propertyChange(PropertyChangeEvent evt)
781    {
782    // Respond to viewport range changes (e.g. alignment panel was scrolled)
783    // Both scrolling and resizing change viewport ranges: scrolling changes
784    // both start and end points, but resize only changes end values.
785    // Here we only want to fastpaint on a scroll, with resize using a normal
786    // paint, so scroll events are identified as changes to the horizontal or
787    // vertical start value.
788  0 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
789    {
790  0 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
791    }
792  0 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
793    {
794  0 fastPaint(((int[]) evt.getNewValue())[0]
795    - ((int[]) evt.getOldValue())[0]);
796    }
797  0 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
798    {
799  0 repaint();
800    }
801    }
802    }