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