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