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