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