Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.gui

File AnnotationPanel.java

 

Coverage histogram

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

Code metrics

196
387
33
2
1,269
884
166
0.43
11.73
16.5
5.03

Classes

Class Line # Actions
AnnotationPanel 74 387 166 479
0.222402622.2%
AnnotationPanel.DragMode 78 0 0 0
-1.0 -
 

Contributing tests

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