JAL-1989 more unit tests and encapsulation for ColumnSelection
[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.MessageManager;
30
31 import java.awt.AlphaComposite;
32 import java.awt.Color;
33 import java.awt.Dimension;
34 import java.awt.FontMetrics;
35 import java.awt.Graphics;
36 import java.awt.Graphics2D;
37 import java.awt.Image;
38 import java.awt.Rectangle;
39 import java.awt.RenderingHints;
40 import java.awt.event.ActionEvent;
41 import java.awt.event.ActionListener;
42 import java.awt.event.AdjustmentEvent;
43 import java.awt.event.AdjustmentListener;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.awt.event.MouseWheelEvent;
48 import java.awt.event.MouseWheelListener;
49 import java.awt.image.BufferedImage;
50
51 import javax.swing.JColorChooser;
52 import javax.swing.JMenuItem;
53 import javax.swing.JOptionPane;
54 import javax.swing.JPanel;
55 import javax.swing.JPopupMenu;
56 import javax.swing.Scrollable;
57 import javax.swing.SwingUtilities;
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 OR SHEET
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     av.getAlignment().validateAnnotation(aa[activeRow]);
417     ap.alignmentChanged();
418
419     adjustPanelHeight();
420     repaint();
421
422     return;
423   }
424
425   private String collectAnnotVals(Annotation[] anot, String label2)
426   {
427     String collatedInput = "";
428     String last = "";
429     ColumnSelection viscols = av.getColumnSelection();
430     // TODO: refactor and save av.getColumnSelection for efficiency
431     for (int index : viscols.getSelected())
432     {
433       // always check for current display state - just in case
434       if (!viscols.isVisible(index))
435       {
436         continue;
437       }
438       String tlabel = null;
439       if (anot[index] != null)
440       { // LML added stem code
441         if (label2.equals(HELIX) || label2.equals(SHEET)
442                 || label2.equals(STEM) || label2.equals(LABEL))
443         {
444           tlabel = anot[index].description;
445           if (tlabel == null || tlabel.length() < 1)
446           {
447             if (label2.equals(HELIX) || label2.equals(SHEET)
448                     || label2.equals(STEM))
449             {
450               tlabel = "" + anot[index].secondaryStructure;
451             }
452             else
453             {
454               tlabel = "" + anot[index].displayCharacter;
455             }
456           }
457         }
458         if (tlabel != null && !tlabel.equals(last))
459         {
460           if (last.length() > 0)
461           {
462             collatedInput += " ";
463           }
464           collatedInput += tlabel;
465         }
466       }
467     }
468     return collatedInput;
469   }
470
471   /**
472    * DOCUMENT ME!
473    * 
474    * @param evt
475    *          DOCUMENT ME!
476    */
477   @Override
478   public void mousePressed(MouseEvent evt)
479   {
480
481     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
482     if (aa == null)
483     {
484       return;
485     }
486
487     int height = 0;
488     activeRow = -1;
489
490     for (int i = 0; i < aa.length; i++)
491     {
492       if (aa[i].visible)
493       {
494         height += aa[i].height;
495       }
496
497       if (evt.getY() < height)
498       {
499         if (aa[i].editable)
500         {
501           activeRow = i;
502         }
503         else if (aa[i].graph > 0)
504         {
505           // Stretch Graph
506           graphStretch = i;
507           graphStretchY = evt.getY();
508         }
509
510         break;
511       }
512     }
513
514     if (SwingUtilities.isRightMouseButton(evt) && activeRow != -1)
515     {
516       if (av.getColumnSelection() == null)
517       {
518         return;
519       }
520
521       JPopupMenu pop = new JPopupMenu(
522               MessageManager.getString("label.structure_type"));
523       JMenuItem item;
524       /*
525        * Just display the needed structure options
526        */
527       if (av.getAlignment().isNucleotide() == true)
528       {
529         item = new JMenuItem(STEM);
530         item.addActionListener(this);
531         pop.add(item);
532       }
533       else
534       {
535         item = new JMenuItem(HELIX);
536         item.addActionListener(this);
537         pop.add(item);
538         item = new JMenuItem(SHEET);
539         item.addActionListener(this);
540         pop.add(item);
541       }
542       item = new JMenuItem(LABEL);
543       item.addActionListener(this);
544       pop.add(item);
545       item = new JMenuItem(COLOUR);
546       item.addActionListener(this);
547       pop.add(item);
548       item = new JMenuItem(REMOVE);
549       item.addActionListener(this);
550       pop.add(item);
551       pop.show(this, evt.getX(), evt.getY());
552
553       return;
554     }
555
556     if (aa == null)
557     {
558       return;
559     }
560
561     ap.getScalePanel().mousePressed(evt);
562
563   }
564
565   /**
566    * DOCUMENT ME!
567    * 
568    * @param evt
569    *          DOCUMENT ME!
570    */
571   @Override
572   public void mouseReleased(MouseEvent evt)
573   {
574     graphStretch = -1;
575     graphStretchY = -1;
576     mouseDragging = false;
577     ap.getScalePanel().mouseReleased(evt);
578   }
579
580   /**
581    * DOCUMENT ME!
582    * 
583    * @param evt
584    *          DOCUMENT ME!
585    */
586   @Override
587   public void mouseEntered(MouseEvent evt)
588   {
589     ap.getScalePanel().mouseEntered(evt);
590   }
591
592   /**
593    * DOCUMENT ME!
594    * 
595    * @param evt
596    *          DOCUMENT ME!
597    */
598   @Override
599   public void mouseExited(MouseEvent evt)
600   {
601     ap.getScalePanel().mouseExited(evt);
602   }
603
604   /**
605    * DOCUMENT ME!
606    * 
607    * @param evt
608    *          DOCUMENT ME!
609    */
610   @Override
611   public void mouseDragged(MouseEvent evt)
612   {
613     if (graphStretch > -1)
614     {
615       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
616               - evt.getY();
617       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
618       {
619         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
620       }
621       graphStretchY = evt.getY();
622       adjustPanelHeight();
623       ap.paintAlignment(true);
624     }
625     else
626     {
627       ap.getScalePanel().mouseDragged(evt);
628     }
629   }
630
631   /**
632    * DOCUMENT ME!
633    * 
634    * @param evt
635    *          DOCUMENT ME!
636    */
637   @Override
638   public void mouseMoved(MouseEvent evt)
639   {
640     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
641
642     if (aa == null)
643     {
644       this.setToolTipText(null);
645       return;
646     }
647
648     int row = -1;
649     int height = 0;
650
651     for (int i = 0; i < aa.length; i++)
652     {
653       if (aa[i].visible)
654       {
655         height += aa[i].height;
656       }
657
658       if (evt.getY() < height)
659       {
660         row = i;
661
662         break;
663       }
664     }
665
666     if (row == -1)
667     {
668       this.setToolTipText(null);
669       return;
670     }
671
672     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
673
674     if (av.hasHiddenColumns())
675     {
676       res = av.getColumnSelection().adjustForHiddenColumns(res);
677     }
678
679     if (row > -1 && aa[row].annotations != null
680             && res < aa[row].annotations.length)
681     {
682       if (aa[row].graphGroup > -1)
683       {
684         StringBuffer tip = new StringBuffer("<html>");
685         for (int gg = 0; gg < aa.length; gg++)
686         {
687           if (aa[gg].graphGroup == aa[row].graphGroup
688                   && aa[gg].annotations[res] != null)
689           {
690             tip.append(aa[gg].label + " "
691                     + aa[gg].annotations[res].description + "<br>");
692           }
693         }
694         if (tip.length() != 6)
695         {
696           tip.setLength(tip.length() - 4);
697           this.setToolTipText(tip.toString() + "</html>");
698         }
699       }
700       else if (aa[row].annotations[res] != null
701               && aa[row].annotations[res].description != null
702               && aa[row].annotations[res].description.length() > 0)
703       {
704         this.setToolTipText(JvSwingUtils.wrapTooltip(true,
705                 aa[row].annotations[res].description));
706       }
707       else
708       {
709         // clear the tooltip.
710         this.setToolTipText(null);
711       }
712
713       if (aa[row].annotations[res] != null)
714       {
715         StringBuffer text = new StringBuffer("Sequence position "
716                 + (res + 1));
717
718         if (aa[row].annotations[res].description != null)
719         {
720           text.append("  " + aa[row].annotations[res].description);
721         }
722
723         ap.alignFrame.statusBar.setText(text.toString());
724       }
725     }
726     else
727     {
728       this.setToolTipText(null);
729     }
730   }
731
732   /**
733    * DOCUMENT ME!
734    * 
735    * @param evt
736    *          DOCUMENT ME!
737    */
738   @Override
739   public void mouseClicked(MouseEvent evt)
740   {
741     // if (activeRow != -1)
742     // {
743     // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
744     // AlignmentAnnotation anot = aa[activeRow];
745     // }
746   }
747
748   // TODO mouseClicked-content and drawCursor are quite experimental!
749   public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
750           int y1)
751   {
752     int pady = av.getCharHeight() / 5;
753     int charOffset = 0;
754     graphics.setColor(Color.black);
755     graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
756
757     if (av.validCharWidth)
758     {
759       graphics.setColor(Color.white);
760
761       char s = seq.getCharAt(res);
762
763       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
764       graphics.drawString(String.valueOf(s), charOffset + x1,
765               (y1 + av.getCharHeight()) - pady);
766     }
767
768   }
769
770   private volatile boolean imageFresh = false;
771
772   /**
773    * DOCUMENT ME!
774    * 
775    * @param g
776    *          DOCUMENT ME!
777    */
778   @Override
779   public void paintComponent(Graphics g)
780   {
781     g.setColor(Color.white);
782     g.fillRect(0, 0, getWidth(), getHeight());
783
784     if (image != null)
785     {
786       if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
787               || (getVisibleRect().height != g.getClipBounds().height))
788       {
789         g.drawImage(image, 0, 0, this);
790         fastPaint = false;
791         return;
792       }
793     }
794     imgWidth = (av.endRes - av.startRes + 1) * av.getCharWidth();
795     if (imgWidth < 1)
796     {
797       return;
798     }
799     if (image == null || imgWidth != image.getWidth(this)
800             || image.getHeight(this) != getHeight())
801     {
802       try
803       {
804         image = new BufferedImage(imgWidth, ap.getAnnotationPanel()
805                 .getHeight(), BufferedImage.TYPE_INT_RGB);
806       } catch (OutOfMemoryError oom)
807       {
808         try
809         {
810           System.gc();
811         } catch (Exception x)
812         {
813         }
814         ;
815         new OOMWarning(
816                 "Couldn't allocate memory to redraw screen. Please restart Jalview",
817                 oom);
818         return;
819       }
820       gg = (Graphics2D) image.getGraphics();
821
822       if (av.antiAlias)
823       {
824         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
825                 RenderingHints.VALUE_ANTIALIAS_ON);
826       }
827
828       gg.setFont(av.getFont());
829       fm = gg.getFontMetrics();
830       gg.setColor(Color.white);
831       gg.fillRect(0, 0, imgWidth, image.getHeight());
832       imageFresh = true;
833     }
834
835     drawComponent(gg, av.startRes, av.endRes + 1);
836     imageFresh = false;
837     g.drawImage(image, 0, 0, this);
838   }
839
840   /**
841    * set true to enable redraw timing debug output on stderr
842    */
843   private final boolean debugRedraw = false;
844
845   /**
846    * non-Thread safe repaint
847    * 
848    * @param horizontal
849    *          repaint with horizontal shift in alignment
850    */
851   public void fastPaint(int horizontal)
852   {
853     if ((horizontal == 0) || gg == null
854             || av.getAlignment().getAlignmentAnnotation() == null
855             || av.getAlignment().getAlignmentAnnotation().length < 1
856             || av.isCalcInProgress())
857     {
858       repaint();
859       return;
860     }
861     long stime = System.currentTimeMillis();
862     gg.copyArea(0, 0, imgWidth, getHeight(),
863             -horizontal * av.getCharWidth(), 0);
864     long mtime = System.currentTimeMillis();
865     int sr = av.startRes;
866     int er = av.endRes + 1;
867     int transX = 0;
868
869     if (horizontal > 0) // scrollbar pulled right, image to the left
870     {
871       transX = (er - sr - horizontal) * av.getCharWidth();
872       sr = er - horizontal;
873     }
874     else if (horizontal < 0)
875     {
876       er = sr - horizontal;
877     }
878
879     gg.translate(transX, 0);
880
881     drawComponent(gg, sr, er);
882
883     gg.translate(-transX, 0);
884     long dtime = System.currentTimeMillis();
885     fastPaint = true;
886     repaint();
887     long rtime = System.currentTimeMillis();
888     if (debugRedraw)
889     {
890       System.err.println("Scroll:\t" + horizontal + "\tCopyArea:\t"
891               + (mtime - stime) + "\tDraw component:\t" + (dtime - mtime)
892               + "\tRepaint call:\t" + (rtime - dtime));
893     }
894
895   }
896
897   private volatile boolean lastImageGood = false;
898
899   /**
900    * DOCUMENT ME!
901    * 
902    * @param g
903    *          DOCUMENT ME!
904    * @param startRes
905    *          DOCUMENT ME!
906    * @param endRes
907    *          DOCUMENT ME!
908    */
909   public void drawComponent(Graphics g, int startRes, int endRes)
910   {
911     BufferedImage oldFaded = fadedImage;
912     if (av.isCalcInProgress())
913     {
914       if (image == null)
915       {
916         lastImageGood = false;
917         return;
918       }
919       // We'll keep a record of the old image,
920       // and draw a faded image until the calculation
921       // has completed
922       if (lastImageGood
923               && (fadedImage == null || fadedImage.getWidth() != imgWidth || fadedImage
924                       .getHeight() != image.getHeight()))
925       {
926         // System.err.println("redraw faded image ("+(fadedImage==null ?
927         // "null image" : "") + " lastGood="+lastImageGood+")");
928         fadedImage = new BufferedImage(imgWidth, image.getHeight(),
929                 BufferedImage.TYPE_INT_RGB);
930
931         Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
932
933         fadedG.setColor(Color.white);
934         fadedG.fillRect(0, 0, imgWidth, image.getHeight());
935
936         fadedG.setComposite(AlphaComposite.getInstance(
937                 AlphaComposite.SRC_OVER, .3f));
938         fadedG.drawImage(image, 0, 0, this);
939
940       }
941       // make sure we don't overwrite the last good faded image until all
942       // calculations have finished
943       lastImageGood = false;
944
945     }
946     else
947     {
948       if (fadedImage != null)
949       {
950         oldFaded = fadedImage;
951       }
952       fadedImage = null;
953     }
954
955     g.setColor(Color.white);
956     g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight());
957
958     g.setFont(av.getFont());
959     if (fm == null)
960     {
961       fm = g.getFontMetrics();
962     }
963
964     if ((av.getAlignment().getAlignmentAnnotation() == null)
965             || (av.getAlignment().getAlignmentAnnotation().length < 1))
966     {
967       g.setColor(Color.white);
968       g.fillRect(0, 0, getWidth(), getHeight());
969       g.setColor(Color.black);
970       if (av.validCharWidth)
971       {
972         g.drawString(MessageManager
973                 .getString("label.alignment_has_no_annotations"), 20, 15);
974       }
975
976       return;
977     }
978     lastImageGood = renderer.drawComponent(this, av, g, activeRow,
979             startRes, endRes);
980     if (!lastImageGood && fadedImage == null)
981     {
982       fadedImage = oldFaded;
983     }
984   }
985
986   @Override
987   public FontMetrics getFontMetrics()
988   {
989     return fm;
990   }
991
992   @Override
993   public Image getFadedImage()
994   {
995     return fadedImage;
996   }
997
998   @Override
999   public int getFadedImageWidth()
1000   {
1001     return imgWidth;
1002   }
1003
1004   private int[] bounds = new int[2];
1005
1006   @Override
1007   public int[] getVisibleVRange()
1008   {
1009     if (ap != null && ap.getAlabels() != null)
1010     {
1011       int sOffset = -ap.getAlabels().getScrollOffset();
1012       int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
1013       bounds[0] = sOffset;
1014       bounds[1] = visHeight;
1015       return bounds;
1016     }
1017     else
1018     {
1019       return null;
1020     }
1021   }
1022 }