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