Clover icon

Coverage Report

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

File AnnotationPanel.java

 

Coverage histogram

../../img/srcFileCovDistChart3.png
47% of files have more coverage

Code metrics

206
404
35
2
1,351
916
174
0.43
11.54
17.5
4.97

Classes

Class Line # Actions
AnnotationPanel 76 404 174
0.2697674327%
AnnotationPanel.DragMode 80 0 0
-1.0 -
 

Contributing tests

This file is covered by 122 tests. .

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 java.awt.AlphaComposite;
24    import java.awt.Color;
25    import java.awt.Dimension;
26    import java.awt.FontMetrics;
27    import java.awt.Graphics;
28    import java.awt.Graphics2D;
29    import java.awt.Image;
30    import java.awt.Rectangle;
31    import java.awt.RenderingHints;
32    import java.awt.event.ActionEvent;
33    import java.awt.event.ActionListener;
34    import java.awt.event.AdjustmentEvent;
35    import java.awt.event.AdjustmentListener;
36    import java.awt.event.MouseEvent;
37    import java.awt.event.MouseListener;
38    import java.awt.event.MouseMotionListener;
39    import java.awt.event.MouseWheelEvent;
40    import java.awt.event.MouseWheelListener;
41    import java.awt.image.BufferedImage;
42    import java.beans.PropertyChangeEvent;
43    import java.util.ArrayList;
44    import java.util.Collections;
45    import java.util.List;
46   
47    import javax.swing.JMenuItem;
48    import javax.swing.JPanel;
49    import javax.swing.JPopupMenu;
50    import javax.swing.Scrollable;
51    import javax.swing.ToolTipManager;
52   
53    import jalview.datamodel.AlignmentAnnotation;
54    import jalview.datamodel.AlignmentI;
55    import jalview.datamodel.Annotation;
56    import jalview.datamodel.ColumnSelection;
57    import jalview.datamodel.HiddenColumns;
58    import jalview.datamodel.SequenceI;
59    import jalview.gui.JalviewColourChooser.ColourChooserListener;
60    import jalview.renderer.AnnotationRenderer;
61    import jalview.renderer.AwtRenderPanelI;
62    import jalview.schemes.ResidueProperties;
63    import jalview.util.Comparison;
64    import jalview.util.MessageManager;
65    import jalview.util.Platform;
66    import jalview.viewmodel.ViewportListenerI;
67    import jalview.viewmodel.ViewportRanges;
68   
69    /**
70    * AnnotationPanel displays visible portion of annotation rows below unwrapped
71    * alignment
72    *
73    * @author $author$
74    * @version $Revision$
75    */
 
76    public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
77    MouseListener, MouseWheelListener, MouseMotionListener,
78    ActionListener, AdjustmentListener, Scrollable, ViewportListenerI
79    {
 
80    enum DragMode
81    {
82    Select, Resize, Undefined
83    };
84   
85    String HELIX = MessageManager.getString("label.helix");
86   
87    String SHEET = MessageManager.getString("label.sheet");
88   
89    /**
90    * For RNA secondary structure "stems" aka helices
91    */
92    String STEM = MessageManager.getString("label.rna_helix");
93   
94    String LABEL = MessageManager.getString("label.label");
95   
96    String REMOVE = MessageManager.getString("label.remove_annotation");
97   
98    String COLOUR = MessageManager.getString("action.colour");
99   
100    public final Color HELIX_COLOUR = Color.red.darker();
101   
102    public final Color SHEET_COLOUR = Color.green.darker().darker();
103   
104    public final Color STEM_COLOUR = Color.blue.darker();
105   
106    /** DOCUMENT ME!! */
107    public AlignViewport av;
108   
109    AlignmentPanel ap;
110   
111    public int activeRow = -1;
112   
113    public BufferedImage image;
114   
115    public volatile BufferedImage fadedImage;
116   
117    // private Graphics2D gg;
118   
119    public FontMetrics fm;
120   
121    public int imgWidth = 0;
122   
123    boolean fastPaint = false;
124   
125    // Used For mouse Dragging and resizing graphs
126    int graphStretch = -1;
127   
128    int mouseDragLastX = -1;
129   
130    int mouseDragLastY = -1;
131   
132    DragMode dragMode = DragMode.Undefined;
133   
134    boolean mouseDragging = false;
135   
136    // for editing cursor
137    int cursorX = 0;
138   
139    int cursorY = 0;
140   
141    public final AnnotationRenderer renderer;
142   
143    private MouseWheelListener[] _mwl;
144   
145    /**
146    * Creates a new AnnotationPanel object.
147    *
148    * @param ap
149    * DOCUMENT ME!
150    */
 
151  267 toggle public AnnotationPanel(AlignmentPanel ap)
152    {
153  267 ToolTipManager.sharedInstance().registerComponent(this);
154  267 ToolTipManager.sharedInstance().setInitialDelay(0);
155  267 ToolTipManager.sharedInstance().setDismissDelay(10000);
156  267 this.ap = ap;
157  267 av = ap.av;
158  267 this.setLayout(null);
159  267 addMouseListener(this);
160  267 addMouseMotionListener(this);
161  267 ap.annotationScroller.getVerticalScrollBar()
162    .addAdjustmentListener(this);
163    // save any wheel listeners on the scroller, so we can propagate scroll
164    // events to them.
165  267 _mwl = ap.annotationScroller.getMouseWheelListeners();
166    // and then set our own listener to consume all mousewheel events
167  267 ap.annotationScroller.addMouseWheelListener(this);
168  267 renderer = new AnnotationRenderer();
169   
170  267 av.getRanges().addPropertyChangeListener(this);
171    }
172   
 
173  9 toggle public AnnotationPanel(AlignViewport av)
174    {
175  9 this.av = av;
176  9 renderer = new AnnotationRenderer();
177    }
178   
 
179  0 toggle @Override
180    public void mouseWheelMoved(MouseWheelEvent e)
181    {
182  0 if (e.isShiftDown())
183    {
184  0 e.consume();
185  0 double wheelRotation = e.getPreciseWheelRotation();
186  0 if (wheelRotation > 0)
187    {
188  0 av.getRanges().scrollRight(true);
189    }
190  0 else if (wheelRotation < 0)
191    {
192  0 av.getRanges().scrollRight(false);
193    }
194    }
195    else
196    {
197    // TODO: find the correct way to let the event bubble up to
198    // ap.annotationScroller
199  0 for (MouseWheelListener mwl : _mwl)
200    {
201  0 if (mwl != null)
202    {
203  0 mwl.mouseWheelMoved(e);
204    }
205  0 if (e.isConsumed())
206    {
207  0 break;
208    }
209    }
210    }
211    }
212   
 
213  0 toggle @Override
214    public Dimension getPreferredScrollableViewportSize()
215    {
216  0 Dimension ps = getPreferredSize();
217  0 return new Dimension(ps.width, adjustForAlignFrame(false, ps.height));
218    }
219   
 
220  0 toggle @Override
221    public int getScrollableBlockIncrement(Rectangle visibleRect,
222    int orientation, int direction)
223    {
224  0 return 30;
225    }
226   
 
227  1985 toggle @Override
228    public boolean getScrollableTracksViewportHeight()
229    {
230  1985 return false;
231    }
232   
 
233  1985 toggle @Override
234    public boolean getScrollableTracksViewportWidth()
235    {
236  1985 return true;
237    }
238   
 
239  0 toggle @Override
240    public int getScrollableUnitIncrement(Rectangle visibleRect,
241    int orientation, int direction)
242    {
243  0 return 30;
244    }
245   
246    /*
247    * (non-Javadoc)
248    *
249    * @see
250    * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event
251    * .AdjustmentEvent)
252    */
 
253  448 toggle @Override
254    public void adjustmentValueChanged(AdjustmentEvent evt)
255    {
256    // update annotation label display
257  448 ap.getAlabels().setScrollOffset(-evt.getValue());
258    }
259   
260    /**
261    * Calculates the height of the annotation displayed in the annotation panel.
262    * Callers should normally call the ap.adjustAnnotationHeight method to ensure
263    * all annotation associated components are updated correctly.
264    *
265    */
 
266  1741 toggle public int adjustPanelHeight()
267    {
268  1741 int height = av.calcPanelHeight();
269  1741 this.setPreferredSize(new Dimension(1, height));
270  1741 if (ap != null)
271    {
272    // revalidate only when the alignment panel is fully constructed
273  1420 ap.validate();
274    }
275   
276  1741 return height;
277    }
278   
279    /**
280    * DOCUMENT ME!
281    *
282    * @param evt
283    * DOCUMENT ME!
284    */
 
285  0 toggle @Override
286    public void actionPerformed(ActionEvent evt)
287    {
288  0 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
289  0 if (aa == null)
290    {
291  0 return;
292    }
293  0 Annotation[] anot = aa[activeRow].annotations;
294   
295  0 if (anot.length < av.getColumnSelection().getMax())
296    {
297  0 Annotation[] temp = new Annotation[av.getColumnSelection().getMax()
298    + 2];
299  0 System.arraycopy(anot, 0, temp, 0, anot.length);
300  0 anot = temp;
301  0 aa[activeRow].annotations = anot;
302    }
303   
304  0 String action = evt.getActionCommand();
305  0 if (action.equals(REMOVE))
306    {
307  0 for (int index : av.getColumnSelection().getSelected())
308    {
309  0 if (av.getAlignment().getHiddenColumns().isVisible(index))
310    {
311  0 anot[index] = null;
312    }
313    }
314    }
315  0 else if (action.equals(LABEL))
316    {
317  0 String exMesg = collectAnnotVals(anot, LABEL);
318  0 String label = JvOptionPane.showInputDialog(
319    MessageManager.getString("label.enter_label"), exMesg);
320   
321  0 if (label == null)
322    {
323  0 return;
324    }
325   
326  0 if ((label.length() > 0) && !aa[activeRow].hasText)
327    {
328  0 aa[activeRow].hasText = true;
329    }
330   
331  0 for (int index : av.getColumnSelection().getSelected())
332    {
333  0 if (!av.getAlignment().getHiddenColumns().isVisible(index))
334    {
335  0 continue;
336    }
337   
338  0 if (anot[index] == null)
339    {
340  0 anot[index] = new Annotation(label, "", ' ', 0);
341    }
342    else
343    {
344  0 anot[index].displayCharacter = label;
345    }
346    }
347    }
348  0 else if (action.equals(COLOUR))
349    {
350  0 final Annotation[] fAnot = anot;
351  0 String title = MessageManager
352    .getString("label.select_foreground_colour");
353  0 ColourChooserListener listener = new ColourChooserListener()
354    {
 
355  0 toggle @Override
356    public void colourSelected(Color c)
357    {
358  0 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
359  0 for (int index : av.getColumnSelection().getSelected())
360    {
361  0 if (hiddenColumns.isVisible(index))
362    {
363  0 if (fAnot[index] == null)
364    {
365  0 fAnot[index] = new Annotation("", "", ' ', 0);
366    }
367  0 fAnot[index].colour = c;
368    }
369    }};
370    };
371  0 JalviewColourChooser.showColourChooser(this,
372    title, Color.black, listener);
373    }
374    else
375    // HELIX, SHEET or STEM
376    {
377  0 char type = 0;
378  0 String symbol = "\u03B1"; // alpha
379   
380  0 if (action.equals(HELIX))
381    {
382  0 type = 'H';
383    }
384  0 else if (action.equals(SHEET))
385    {
386  0 type = 'E';
387  0 symbol = "\u03B2"; // beta
388    }
389   
390    // Added by LML to color stems
391  0 else if (action.equals(STEM))
392    {
393  0 type = 'S';
394  0 int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
395  0 symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
396    }
397   
398  0 if (!aa[activeRow].hasIcons)
399    {
400  0 aa[activeRow].hasIcons = true;
401    }
402   
403  0 String label = JvOptionPane.showInputDialog(MessageManager
404    .getString("label.enter_label_for_the_structure"), symbol);
405   
406  0 if (label == null)
407    {
408  0 return;
409    }
410   
411  0 if ((label.length() > 0) && !aa[activeRow].hasText)
412    {
413  0 aa[activeRow].hasText = true;
414  0 if (action.equals(STEM))
415    {
416  0 aa[activeRow].showAllColLabels = true;
417    }
418    }
419  0 for (int index : av.getColumnSelection().getSelected())
420    {
421  0 if (!av.getAlignment().getHiddenColumns().isVisible(index))
422    {
423  0 continue;
424    }
425   
426  0 if (anot[index] == null)
427    {
428  0 anot[index] = new Annotation(label, "", type, 0);
429    }
430   
431  0 anot[index].secondaryStructure = type != 'S' ? type
432  0 : label.length() == 0 ? ' ' : label.charAt(0);
433  0 anot[index].displayCharacter = label;
434   
435    }
436    }
437   
438  0 av.getAlignment().validateAnnotation(aa[activeRow]);
439  0 ap.alignmentChanged();
440  0 ap.alignFrame.setMenusForViewport();
441  0 adjustPanelHeight();
442  0 repaint();
443   
444  0 return;
445    }
446   
447    /**
448    * Returns any existing annotation concatenated as a string. For each
449    * annotation, takes the description, if any, else the secondary structure
450    * character (if type is HELIX, SHEET or STEM), else the display character (if
451    * type is LABEL).
452    *
453    * @param anots
454    * @param type
455    * @return
456    */
 
457  0 toggle private String collectAnnotVals(Annotation[] anots, String type)
458    {
459    // TODO is this method wanted? why? 'last' is not used
460   
461  0 StringBuilder collatedInput = new StringBuilder(64);
462  0 String last = "";
463  0 ColumnSelection viscols = av.getColumnSelection();
464  0 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
465   
466    /*
467    * the selection list (read-only view) is in selection order, not
468    * column order; make a copy so we can sort it
469    */
470  0 List<Integer> selected = new ArrayList<>(viscols.getSelected());
471  0 Collections.sort(selected);
472  0 for (int index : selected)
473    {
474    // always check for current display state - just in case
475  0 if (!hidden.isVisible(index))
476    {
477  0 continue;
478    }
479  0 String tlabel = null;
480  0 if (anots[index] != null)
481    { // LML added stem code
482  0 if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM)
483    || type.equals(LABEL))
484    {
485  0 tlabel = anots[index].description;
486  0 if (tlabel == null || tlabel.length() < 1)
487    {
488  0 if (type.equals(HELIX) || type.equals(SHEET)
489    || type.equals(STEM))
490    {
491  0 tlabel = "" + anots[index].secondaryStructure;
492    }
493    else
494    {
495  0 tlabel = "" + anots[index].displayCharacter;
496    }
497    }
498    }
499  0 if (tlabel != null && !tlabel.equals(last))
500    {
501  0 if (last.length() > 0)
502    {
503  0 collatedInput.append(" ");
504    }
505  0 collatedInput.append(tlabel);
506    }
507    }
508    }
509  0 return collatedInput.toString();
510    }
511   
512    /**
513    * Action on right mouse pressed on Mac is to show a pop-up menu for the
514    * annotation. Action on left mouse pressed is to find which annotation is
515    * pressed and mark the start of a column selection or graph resize operation.
516    *
517    * @param evt
518    */
 
519  0 toggle @Override
520    public void mousePressed(MouseEvent evt)
521    {
522   
523  0 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
524  0 if (aa == null)
525    {
526  0 return;
527    }
528  0 mouseDragLastX = evt.getX();
529  0 mouseDragLastY = evt.getY();
530   
531    /*
532    * add visible annotation heights until we reach the y
533    * position, to find which annotation it is in
534    */
535  0 int height = 0;
536  0 activeRow = -1;
537   
538  0 final int y = evt.getY();
539  0 for (int i = 0; i < aa.length; i++)
540    {
541  0 if (aa[i].visible)
542    {
543  0 height += aa[i].height;
544    }
545   
546  0 if (y < height)
547    {
548  0 if (aa[i].editable)
549    {
550  0 activeRow = i;
551    }
552  0 else if (aa[i].graph > 0)
553    {
554    /*
555    * we have clicked on a resizable graph annotation
556    */
557  0 graphStretch = i;
558    }
559  0 break;
560    }
561    }
562   
563    /*
564    * isPopupTrigger fires in mousePressed on Mac,
565    * not until mouseRelease on Windows
566    */
567  0 if (evt.isPopupTrigger() && activeRow != -1)
568    {
569  0 showPopupMenu(y, evt.getX());
570  0 return;
571    }
572   
573  0 ap.getScalePanel().mousePressed(evt);
574    }
575   
576    /**
577    * Construct and display a context menu at the right-click position
578    *
579    * @param y
580    * @param x
581    */
 
582  0 toggle void showPopupMenu(final int y, int x)
583    {
584  0 if (av.getColumnSelection() == null
585    || av.getColumnSelection().isEmpty())
586    {
587  0 return;
588    }
589   
590  0 JPopupMenu pop = new JPopupMenu(
591    MessageManager.getString("label.structure_type"));
592  0 JMenuItem item;
593    /*
594    * Just display the needed structure options
595    */
596  0 if (av.getAlignment().isNucleotide())
597    {
598  0 item = new JMenuItem(STEM);
599  0 item.addActionListener(this);
600  0 pop.add(item);
601    }
602    else
603    {
604  0 item = new JMenuItem(HELIX);
605  0 item.addActionListener(this);
606  0 pop.add(item);
607  0 item = new JMenuItem(SHEET);
608  0 item.addActionListener(this);
609  0 pop.add(item);
610    }
611  0 item = new JMenuItem(LABEL);
612  0 item.addActionListener(this);
613  0 pop.add(item);
614  0 item = new JMenuItem(COLOUR);
615  0 item.addActionListener(this);
616  0 pop.add(item);
617  0 item = new JMenuItem(REMOVE);
618  0 item.addActionListener(this);
619  0 pop.add(item);
620  0 pop.show(this, x, y);
621    }
622   
623    /**
624    * Action on mouse up is to clear mouse drag data and call mouseReleased on
625    * ScalePanel, to deal with defining the selection group (if any) defined by
626    * the mouse drag
627    *
628    * @param evt
629    */
 
630  0 toggle @Override
631    public void mouseReleased(MouseEvent evt)
632    {
633  0 graphStretch = -1;
634  0 mouseDragLastX = -1;
635  0 mouseDragLastY = -1;
636  0 mouseDragging = false;
637  0 dragMode = DragMode.Undefined;
638  0 ap.getScalePanel().mouseReleased(evt);
639   
640    /*
641    * isPopupTrigger is set in mouseReleased on Windows
642    * (in mousePressed on Mac)
643    */
644  0 if (evt.isPopupTrigger() && activeRow != -1)
645    {
646  0 showPopupMenu(evt.getY(), evt.getX());
647    }
648   
649    }
650   
651    /**
652    * DOCUMENT ME!
653    *
654    * @param evt
655    * DOCUMENT ME!
656    */
 
657  0 toggle @Override
658    public void mouseEntered(MouseEvent evt)
659    {
660  0 this.mouseDragging = false;
661  0 ap.getScalePanel().mouseEntered(evt);
662    }
663   
664    /**
665    * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling
666    * with column selection on a mouse drag
667    *
668    * @param evt
669    */
 
670  0 toggle @Override
671    public void mouseExited(MouseEvent evt)
672    {
673  0 ap.getScalePanel().mouseExited(evt);
674    }
675   
676    /**
677    * DOCUMENT ME!
678    *
679    * @param evt
680    * DOCUMENT ME!
681    */
 
682  0 toggle @Override
683    public void mouseDragged(MouseEvent evt)
684    {
685    /*
686    * todo: if dragMode is Undefined:
687    * - set to Select if dx > dy
688    * - set to Resize if dy > dx
689    * - do nothing if dx == dy
690    */
691  0 final int x = evt.getX();
692  0 final int y = evt.getY();
693  0 if (dragMode == DragMode.Undefined)
694    {
695  0 int dx = Math.abs(x - mouseDragLastX);
696  0 int dy = Math.abs(y - mouseDragLastY);
697  0 if (graphStretch == -1 || dx > dy)
698    {
699    /*
700    * mostly horizontal drag, or not a graph annotation
701    */
702  0 dragMode = DragMode.Select;
703    }
704  0 else if (dy > dx)
705    {
706    /*
707    * mostly vertical drag
708    */
709  0 dragMode = DragMode.Resize;
710    }
711    }
712   
713  0 if (dragMode == DragMode.Undefined)
714    {
715    /*
716    * drag is diagonal - defer deciding whether to
717    * treat as up/down or left/right
718    */
719  0 return;
720    }
721   
722  0 try
723    {
724  0 if (dragMode == DragMode.Resize)
725    {
726    /*
727    * resize graph annotation if mouse was dragged up or down
728    */
729  0 int deltaY = mouseDragLastY - evt.getY();
730  0 if (deltaY != 0)
731    {
732  0 AlignmentAnnotation graphAnnotation = av.getAlignment()
733    .getAlignmentAnnotation()[graphStretch];
734  0 int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY);
735  0 graphAnnotation.graphHeight = newHeight;
736  0 adjustPanelHeight();
737  0 ap.paintAlignment(false, false);
738    }
739    }
740    else
741    {
742    /*
743    * for mouse drag left or right, delegate to
744    * ScalePanel to adjust the column selection
745    */
746  0 ap.getScalePanel().mouseDragged(evt);
747    }
748    } finally
749    {
750  0 mouseDragLastX = x;
751  0 mouseDragLastY = y;
752    }
753    }
754   
755    /**
756    * Constructs the tooltip, and constructs and displays a status message, for
757    * the current mouse position
758    *
759    * @param evt
760    */
 
761  0 toggle @Override
762    public void mouseMoved(MouseEvent evt)
763    {
764  0 int yPos = evt.getY();
765  0 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
766   
767  0 int row = getRowIndex(yPos, aa);
768   
769  0 if (row == -1)
770    {
771  0 this.setToolTipText(null);
772  0 return;
773    }
774   
775  0 int column = (evt.getX() / av.getCharWidth())
776    + av.getRanges().getStartRes();
777  0 column = Math.min(column, av.getRanges().getEndRes());
778   
779  0 if (av.hasHiddenColumns())
780    {
781  0 column = av.getAlignment().getHiddenColumns()
782    .visibleToAbsoluteColumn(column);
783    }
784   
785  0 AlignmentAnnotation ann = aa[row];
786  0 if (row > -1 && ann.annotations != null
787    && column < ann.annotations.length)
788    {
789  0 String toolTip = buildToolTip(ann, column, aa);
790  0 setToolTipText(toolTip == null ? null
791    : JvSwingUtils.wrapTooltip(true, toolTip));
792  0 String msg = getStatusMessage(av.getAlignment(), column, ann);
793  0 ap.alignFrame.setStatus(msg);
794    }
795    else
796    {
797  0 this.setToolTipText(null);
798  0 ap.alignFrame.setStatus(" ");
799    }
800    }
801   
802    /**
803    * Answers the index in the annotations array of the visible annotation at the
804    * given y position. This is done by adding the heights of visible annotations
805    * until the y position has been exceeded. Answers -1 if no annotations are
806    * visible, or the y position is below all annotations.
807    *
808    * @param yPos
809    * @param aa
810    * @return
811    */
 
812  33 toggle static int getRowIndex(int yPos, AlignmentAnnotation[] aa)
813    {
814  33 if (aa == null)
815    {
816  1 return -1;
817    }
818  32 int row = -1;
819  32 int height = 0;
820   
821  79 for (int i = 0; i < aa.length; i++)
822    {
823  75 if (aa[i].visible)
824    {
825  66 height += aa[i].height;
826    }
827   
828  75 if (height > yPos)
829    {
830  28 row = i;
831  28 break;
832    }
833    }
834  32 return row;
835    }
836   
837    /**
838    * Answers a tooltip for the annotation at the current mouse position, not
839    * wrapped in &lt;html&gt; tags (apply if wanted). Answers null if there is no
840    * tooltip to show.
841    *
842    * @param ann
843    * @param column
844    * @param anns
845    */
 
846  0 toggle static String buildToolTip(AlignmentAnnotation ann, int column,
847    AlignmentAnnotation[] anns)
848    {
849  0 String tooltip = null;
850  0 if (ann.graphGroup > -1)
851    {
852  0 StringBuilder tip = new StringBuilder(32);
853  0 boolean first = true;
854  0 for (int i = 0; i < anns.length; i++)
855    {
856  0 if (anns[i].graphGroup == ann.graphGroup
857    && anns[i].annotations[column] != null)
858    {
859  0 if (!first)
860    {
861  0 tip.append("<br>");
862    }
863  0 first = false;
864  0 tip.append(anns[i].label);
865  0 String description = anns[i].annotations[column].description;
866  0 if (description != null && description.length() > 0)
867    {
868  0 tip.append(" ").append(description);
869    }
870    }
871    }
872  0 tooltip = first ? null : tip.toString();
873    }
874  0 else if (column < ann.annotations.length
875    && ann.annotations[column] != null)
876    {
877  0 tooltip = ann.annotations[column].description;
878    }
879   
880  0 return tooltip;
881    }
882   
883    /**
884    * Constructs and returns the status bar message
885    *
886    * @param al
887    * @param column
888    * @param ann
889    */
 
890  0 toggle static String getStatusMessage(AlignmentI al, int column,
891    AlignmentAnnotation ann)
892    {
893    /*
894    * show alignment column and annotation description if any
895    */
896  0 StringBuilder text = new StringBuilder(32);
897  0 text.append(MessageManager.getString("label.column")).append(" ")
898    .append(column + 1);
899   
900  0 if (column < ann.annotations.length && ann.annotations[column] != null)
901    {
902  0 String description = ann.annotations[column].description;
903  0 if (description != null && description.trim().length() > 0)
904    {
905  0 text.append(" ").append(description);
906    }
907    }
908   
909    /*
910    * if the annotation is sequence-specific, show the sequence number
911    * in the alignment, and (if not a gap) the residue and position
912    */
913  0 SequenceI seqref = ann.sequenceRef;
914  0 if (seqref != null)
915    {
916  0 int seqIndex = al.findIndex(seqref);
917  0 if (seqIndex != -1)
918    {
919  0 text.append(", ").append(MessageManager.getString("label.sequence"))
920    .append(" ").append(seqIndex + 1);
921  0 char residue = seqref.getCharAt(column);
922  0 if (!Comparison.isGap(residue))
923    {
924  0 text.append(" ");
925  0 String name;
926  0 if (al.isNucleotide())
927    {
928  0 name = ResidueProperties.nucleotideName
929    .get(String.valueOf(residue));
930  0 text.append(" Nucleotide: ")
931  0 .append(name != null ? name : residue);
932    }
933    else
934    {
935  0 name = 'X' == residue ? "X"
936  0 : ('*' == residue ? "STOP"
937    : ResidueProperties.aa2Triplet
938    .get(String.valueOf(residue)));
939  0 text.append(" Residue: ").append(name != null ? name : residue);
940    }
941  0 int residuePos = seqref.findPosition(column);
942  0 text.append(" (").append(residuePos).append(")");
943    }
944    }
945    }
946   
947  0 return text.toString();
948    }
949   
950    /**
951    * DOCUMENT ME!
952    *
953    * @param evt
954    * DOCUMENT ME!
955    */
 
956  0 toggle @Override
957    public void mouseClicked(MouseEvent evt)
958    {
959    // if (activeRow != -1)
960    // {
961    // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
962    // AlignmentAnnotation anot = aa[activeRow];
963    // }
964    }
965   
966    // TODO mouseClicked-content and drawCursor are quite experimental!
 
967  0 toggle public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
968    int y1)
969    {
970  0 int pady = av.getCharHeight() / 5;
971  0 int charOffset = 0;
972  0 graphics.setColor(Color.black);
973  0 graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
974   
975  0 if (av.validCharWidth)
976    {
977  0 graphics.setColor(Color.white);
978   
979  0 char s = seq.getCharAt(res);
980   
981  0 charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
982  0 graphics.drawString(String.valueOf(s), charOffset + x1,
983    (y1 + av.getCharHeight()) - pady);
984    }
985   
986    }
987   
988    private volatile boolean imageFresh = false;
989    private Rectangle visibleRect = new Rectangle(), clipBounds = new Rectangle();
990   
991    /**
992    * DOCUMENT ME!
993    *
994    * @param g
995    * DOCUMENT ME!
996    */
 
997  474 toggle @Override
998    public void paintComponent(Graphics g)
999    {
1000   
1001    // BH: note that this method is generally recommended to
1002    // call super.paintComponent(g). Otherwise, the children of this
1003    // component will not be rendered. That is not needed here
1004    // because AnnotationPanel does not have any children. It is
1005    // just a JPanel contained in a JViewPort.
1006   
1007  474 computeVisibleRect(visibleRect);
1008   
1009  474 g.setColor(Color.white);
1010  474 g.fillRect(0, 0, visibleRect.width, visibleRect.height);
1011   
1012  474 if (image != null)
1013    {
1014    // BH 2018 optimizing generation of new Rectangle().
1015  ? if (fastPaint || (visibleRect.width != (clipBounds = g.getClipBounds(clipBounds)).width)
1016    || (visibleRect.height != clipBounds.height))
1017    {
1018   
1019   
1020  19 g.drawImage(image, 0, 0, this);
1021  19 fastPaint = false;
1022  19 return;
1023    }
1024    }
1025  455 imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
1026    + 1) * av.getCharWidth();
1027  455 if (imgWidth < 1)
1028    {
1029  0 return;
1030    }
1031  455 Graphics2D gg;
1032  455 if (image == null || imgWidth != image.getWidth(this)
1033    || image.getHeight(this) != getHeight())
1034    {
1035  39 try
1036    {
1037  39 image = new BufferedImage(imgWidth,
1038    ap.getAnnotationPanel().getHeight(),
1039    BufferedImage.TYPE_INT_RGB);
1040    } catch (OutOfMemoryError oom)
1041    {
1042  0 try
1043    {
1044  0 System.gc();
1045    } catch (Exception x)
1046    {
1047    }
1048  0 ;
1049  0 new OOMWarning(
1050    "Couldn't allocate memory to redraw screen. Please restart Jalview",
1051    oom);
1052  0 return;
1053    }
1054  39 gg = (Graphics2D) image.getGraphics();
1055   
1056  39 if (av.antiAlias)
1057    {
1058  0 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1059    RenderingHints.VALUE_ANTIALIAS_ON);
1060    }
1061   
1062  39 gg.setFont(av.getFont());
1063  39 fm = gg.getFontMetrics();
1064  39 gg.setColor(Color.white);
1065  39 gg.fillRect(0, 0, imgWidth, image.getHeight());
1066  39 imageFresh = true;
1067    } else {
1068  416 gg = (Graphics2D) image.getGraphics();
1069   
1070    }
1071   
1072  455 drawComponent(gg, av.getRanges().getStartRes(),
1073    av.getRanges().getEndRes() + 1);
1074  455 gg.dispose();
1075  455 imageFresh = false;
1076  455 g.drawImage(image, 0, 0, this);
1077    }
1078   
1079    /**
1080    * set true to enable redraw timing debug output on stderr
1081    */
1082    private final boolean debugRedraw = false;
1083   
1084    /**
1085    * non-Thread safe repaint
1086    *
1087    * @param horizontal
1088    * repaint with horizontal shift in alignment
1089    */
 
1090  30 toggle public void fastPaint(int horizontal)
1091    {
1092  30 if ((horizontal == 0) || image == null
1093    || av.getAlignment().getAlignmentAnnotation() == null
1094    || av.getAlignment().getAlignmentAnnotation().length < 1
1095    || av.isCalcInProgress())
1096    {
1097  30 repaint();
1098  30 return;
1099    }
1100   
1101  0 int sr = av.getRanges().getStartRes();
1102  0 int er = av.getRanges().getEndRes() + 1;
1103  0 int transX = 0;
1104   
1105  0 Graphics2D gg = (Graphics2D) image.getGraphics();
1106   
1107  0 gg.copyArea(0, 0, imgWidth, getHeight(),
1108    -horizontal * av.getCharWidth(), 0);
1109   
1110  0 if (horizontal > 0) // scrollbar pulled right, image to the left
1111    {
1112  0 transX = (er - sr - horizontal) * av.getCharWidth();
1113  0 sr = er - horizontal;
1114    }
1115  0 else if (horizontal < 0)
1116    {
1117  0 er = sr - horizontal;
1118    }
1119   
1120  0 gg.translate(transX, 0);
1121   
1122  0 drawComponent(gg, sr, er);
1123   
1124  0 gg.translate(-transX, 0);
1125   
1126  0 gg.dispose();
1127   
1128  0 fastPaint = true;
1129   
1130    // Call repaint on alignment panel so that repaints from other alignment
1131    // panel components can be aggregated. Otherwise performance of the overview
1132    // window and others may be adversely affected.
1133  0 av.getAlignPanel().repaint();
1134    }
1135   
1136    private volatile boolean lastImageGood = false;
1137   
1138    /**
1139    * DOCUMENT ME!
1140    *
1141    * @param g
1142    * DOCUMENT ME!
1143    * @param startRes
1144    * DOCUMENT ME!
1145    * @param endRes
1146    * DOCUMENT ME!
1147    */
 
1148  455 toggle public void drawComponent(Graphics g, int startRes, int endRes)
1149    {
1150  455 BufferedImage oldFaded = fadedImage;
1151  455 if (av.isCalcInProgress())
1152    {
1153  42 if (image == null)
1154    {
1155  0 lastImageGood = false;
1156  0 return;
1157    }
1158    // We'll keep a record of the old image,
1159    // and draw a faded image until the calculation
1160    // has completed
1161  42 if (lastImageGood
1162    && (fadedImage == null || fadedImage.getWidth() != imgWidth
1163    || fadedImage.getHeight() != image.getHeight()))
1164    {
1165    // System.err.println("redraw faded image ("+(fadedImage==null ?
1166    // "null image" : "") + " lastGood="+lastImageGood+")");
1167  24 fadedImage = new BufferedImage(imgWidth, image.getHeight(),
1168    BufferedImage.TYPE_INT_RGB);
1169   
1170  24 Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
1171   
1172  24 fadedG.setColor(Color.white);
1173  24 fadedG.fillRect(0, 0, imgWidth, image.getHeight());
1174   
1175  24 fadedG.setComposite(
1176    AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f));
1177  24 fadedG.drawImage(image, 0, 0, this);
1178   
1179    }
1180    // make sure we don't overwrite the last good faded image until all
1181    // calculations have finished
1182  42 lastImageGood = false;
1183   
1184    }
1185    else
1186    {
1187  413 if (fadedImage != null)
1188    {
1189  24 oldFaded = fadedImage;
1190    }
1191  413 fadedImage = null;
1192    }
1193   
1194  455 g.setColor(Color.white);
1195  455 g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
1196   
1197  455 g.setFont(av.getFont());
1198  455 if (fm == null)
1199    {
1200  0 fm = g.getFontMetrics();
1201    }
1202   
1203  455 if ((av.getAlignment().getAlignmentAnnotation() == null)
1204    || (av.getAlignment().getAlignmentAnnotation().length < 1))
1205    {
1206  0 g.setColor(Color.white);
1207  0 g.fillRect(0, 0, getWidth(), getHeight());
1208  0 g.setColor(Color.black);
1209  0 if (av.validCharWidth)
1210    {
1211  0 g.drawString(MessageManager
1212    .getString("label.alignment_has_no_annotations"), 20, 15);
1213    }
1214   
1215  0 return;
1216    }
1217  455 lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes,
1218    endRes);
1219  455 if (!lastImageGood && fadedImage == null)
1220    {
1221  24 fadedImage = oldFaded;
1222    }
1223    }
1224   
 
1225  681 toggle @Override
1226    public FontMetrics getFontMetrics()
1227    {
1228  681 return fm;
1229    }
1230   
 
1231  681 toggle @Override
1232    public Image getFadedImage()
1233    {
1234  681 return fadedImage;
1235    }
1236   
 
1237  681 toggle @Override
1238    public int getFadedImageWidth()
1239    {
1240  681 return imgWidth;
1241    }
1242   
1243    private int[] bounds = new int[2];
1244   
 
1245  1167 toggle @Override
1246    public int[] getVisibleVRange()
1247    {
1248  1167 if (ap != null && ap.getAlabels() != null)
1249    {
1250  949 int sOffset = -ap.getAlabels().getScrollOffset();
1251  949 int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1252  949 bounds[0] = sOffset;
1253  949 bounds[1] = visHeight;
1254  949 return bounds;
1255    }
1256    else
1257    {
1258  218 return null;
1259    }
1260    }
1261   
1262    /**
1263    * Try to ensure any references held are nulled
1264    */
 
1265  102 toggle public void dispose()
1266    {
1267  102 av = null;
1268  102 ap = null;
1269  102 image = null;
1270  102 fadedImage = null;
1271    // gg = null;
1272  102 _mwl = null;
1273   
1274    /*
1275    * I created the renderer so I will dispose of it
1276    */
1277  102 if (renderer != null)
1278    {
1279  102 renderer.dispose();
1280    }
1281    }
1282   
 
1283  406 toggle @Override
1284    public void propertyChange(PropertyChangeEvent evt)
1285    {
1286    // Respond to viewport range changes (e.g. alignment panel was scrolled)
1287    // Both scrolling and resizing change viewport ranges: scrolling changes
1288    // both start and end points, but resize only changes end values.
1289    // Here we only want to fastpaint on a scroll, with resize using a normal
1290    // paint, so scroll events are identified as changes to the horizontal or
1291    // vertical start value.
1292  406 if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
1293    {
1294  30 fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
1295    }
1296  376 else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
1297    {
1298  0 fastPaint(((int[]) evt.getNewValue())[0]
1299    - ((int[]) evt.getOldValue())[0]);
1300    }
1301  376 else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
1302    {
1303  0 repaint();
1304    }
1305    }
1306   
1307    /**
1308    * computes the visible height of the annotation panel
1309    *
1310    * @param adjustPanelHeight
1311    * - when false, just adjust existing height according to other
1312    * windows
1313    * @param annotationHeight
1314    * @return height to use for the ScrollerPreferredVisibleSize
1315    */
 
1316  781 toggle public int adjustForAlignFrame(boolean adjustPanelHeight,
1317    int annotationHeight)
1318    {
1319    /*
1320    * Estimate available height in the AlignFrame for alignment +
1321    * annotations. Deduct an estimate for title bar, menu bar, scale panel,
1322    * hscroll, status bar, insets.
1323    */
1324  781 int stuff = (ap.getViewName() != null ? 30 : 0)
1325  781 + (Platform.isAMacAndNotJS() ? 120 : 140);
1326  781 int availableHeight = ap.alignFrame.getHeight() - stuff;
1327  781 int rowHeight = av.getCharHeight();
1328   
1329  781 if (adjustPanelHeight)
1330    {
1331  462 int alignmentHeight = rowHeight * av.getAlignment().getHeight();
1332   
1333    /*
1334    * If not enough vertical space, maximize annotation height while keeping
1335    * at least two rows of alignment visible
1336    */
1337  462 if (annotationHeight + alignmentHeight > availableHeight)
1338    {
1339  296 annotationHeight = Math.min(annotationHeight,
1340    availableHeight - 2 * rowHeight);
1341    }
1342    }
1343    else
1344    {
1345    // maintain same window layout whilst updating sliders
1346  319 annotationHeight = Math.min(ap.annotationScroller.getSize().height,
1347    availableHeight - 2 * rowHeight);
1348    }
1349  781 return annotationHeight;
1350    }
1351    }