JAL-1517 fix copyright for 2.8.2
[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   final String HELIX = "Helix";
46
47   final String SHEET = "Sheet";
48
49   /**
50    * For RNA secondary structure "stems" aka helices
51    */
52   final String STEM = "RNA Helix";
53
54   final String LABEL = "Label";
55
56   final String REMOVE = "Remove Annotation";
57
58   final String COLOUR = "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, MessageManager.getString("label.enter_label"),
275               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               "Choose 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(
356               MessageManager.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(MessageManager.getString("label.structure_type"));
494       JMenuItem item;
495       /*
496        * Just display the needed structure options
497        */
498       if (av.getAlignment().isNucleotide() == true)
499       {
500         item = new JMenuItem(STEM);
501         item.addActionListener(this);
502         pop.add(item);
503       }
504       else
505       {
506         item = new JMenuItem(HELIX);
507         item.addActionListener(this);
508         pop.add(item);
509         item = new JMenuItem(SHEET);
510         item.addActionListener(this);
511         pop.add(item);
512       }
513       item = new JMenuItem(LABEL);
514       item.addActionListener(this);
515       pop.add(item);
516       item = new JMenuItem(COLOUR);
517       item.addActionListener(this);
518       pop.add(item);
519       item = new JMenuItem(REMOVE);
520       item.addActionListener(this);
521       pop.add(item);
522       pop.show(this, evt.getX(), evt.getY());
523
524       return;
525     }
526
527     if (aa == null)
528     {
529       return;
530     }
531
532     ap.scalePanel.mousePressed(evt);
533
534   }
535
536   /**
537    * DOCUMENT ME!
538    * 
539    * @param evt
540    *          DOCUMENT ME!
541    */
542   @Override
543   public void mouseReleased(MouseEvent evt)
544   {
545     graphStretch = -1;
546     graphStretchY = -1;
547     mouseDragging = false;
548     ap.scalePanel.mouseReleased(evt);
549   }
550
551   /**
552    * DOCUMENT ME!
553    * 
554    * @param evt
555    *          DOCUMENT ME!
556    */
557   @Override
558   public void mouseEntered(MouseEvent evt)
559   {
560     ap.scalePanel.mouseEntered(evt);
561   }
562
563   /**
564    * DOCUMENT ME!
565    * 
566    * @param evt
567    *          DOCUMENT ME!
568    */
569   @Override
570   public void mouseExited(MouseEvent evt)
571   {
572     ap.scalePanel.mouseExited(evt);
573   }
574
575   /**
576    * DOCUMENT ME!
577    * 
578    * @param evt
579    *          DOCUMENT ME!
580    */
581   @Override
582   public void mouseDragged(MouseEvent evt)
583   {
584     if (graphStretch > -1)
585     {
586       av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY
587               - evt.getY();
588       if (av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight < 0)
589       {
590         av.getAlignment().getAlignmentAnnotation()[graphStretch].graphHeight = 0;
591       }
592       graphStretchY = evt.getY();
593       adjustPanelHeight();
594       ap.paintAlignment(true);
595     }
596     else
597     {
598       ap.scalePanel.mouseDragged(evt);
599     }
600   }
601
602   /**
603    * DOCUMENT ME!
604    * 
605    * @param evt
606    *          DOCUMENT ME!
607    */
608   @Override
609   public void mouseMoved(MouseEvent evt)
610   {
611     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
612
613     if (aa == null)
614     {
615       this.setToolTipText(null);
616       return;
617     }
618
619     int row = -1;
620     int height = 0;
621
622     for (int i = 0; i < aa.length; i++)
623     {
624       if (aa[i].visible)
625       {
626         height += aa[i].height;
627       }
628
629       if (evt.getY() < height)
630       {
631         row = i;
632
633         break;
634       }
635     }
636
637     if (row == -1)
638     {
639       this.setToolTipText(null);
640       return;
641     }
642
643     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
644
645     if (av.hasHiddenColumns())
646     {
647       res = av.getColumnSelection().adjustForHiddenColumns(res);
648     }
649
650     if (row > -1 && aa[row].annotations != null
651             && res < aa[row].annotations.length)
652     {
653       if (aa[row].graphGroup > -1)
654       {
655         StringBuffer tip = new StringBuffer("<html>");
656         for (int gg = 0; gg < aa.length; gg++)
657         {
658           if (aa[gg].graphGroup == aa[row].graphGroup
659                   && aa[gg].annotations[res] != null)
660           {
661             tip.append(aa[gg].label + " "
662                     + aa[gg].annotations[res].description + "<br>");
663           }
664         }
665         if (tip.length() != 6)
666         {
667           tip.setLength(tip.length() - 4);
668           this.setToolTipText(tip.toString() + "</html>");
669         }
670       }
671       else if (aa[row].annotations[res] != null
672               && aa[row].annotations[res].description != null
673               && aa[row].annotations[res].description.length() > 0)
674       {
675         this.setToolTipText("<html>"+JvSwingUtils.wrapTooltip(aa[row].annotations[res].description)+"</html>");
676       }
677       else
678       {
679         // clear the tooltip.
680         this.setToolTipText(null);
681       }
682
683       if (aa[row].annotations[res] != null)
684       {
685         StringBuffer text = new StringBuffer("Sequence position "
686                 + (res + 1));
687
688         if (aa[row].annotations[res].description != null)
689         {
690           text.append("  " + aa[row].annotations[res].description);
691         }
692
693         ap.alignFrame.statusBar.setText(text.toString());
694       }
695     }
696     else
697     {
698       this.setToolTipText(null);
699     }
700   }
701
702   /**
703    * DOCUMENT ME!
704    * 
705    * @param evt
706    *          DOCUMENT ME!
707    */
708   @Override
709   public void mouseClicked(MouseEvent evt)
710   {
711 //    if (activeRow != -1)
712 //    {
713 //      AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
714 //      AlignmentAnnotation anot = aa[activeRow];
715 //    }
716   }
717
718   // TODO mouseClicked-content and drawCursor are quite experimental!
719   public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1,
720           int y1)
721   {
722     int pady = av.charHeight / 5;
723     int charOffset = 0;
724     graphics.setColor(Color.black);
725     graphics.fillRect(x1, y1, av.charWidth, av.charHeight);
726
727     if (av.validCharWidth)
728     {
729       graphics.setColor(Color.white);
730
731       char s = seq.getCharAt(res);
732
733       charOffset = (av.charWidth - fm.charWidth(s)) / 2;
734       graphics.drawString(String.valueOf(s), charOffset + x1,
735               (y1 + av.charHeight) - pady);
736     }
737
738   }
739
740   private volatile boolean imageFresh = false;
741
742   /**
743    * DOCUMENT ME!
744    * 
745    * @param g
746    *          DOCUMENT ME!
747    */
748   @Override
749   public void paintComponent(Graphics g)
750   {
751     g.setColor(Color.white);
752     g.fillRect(0, 0, getWidth(), getHeight());
753
754     if (image != null)
755     {
756       if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
757               || (getVisibleRect().height != g.getClipBounds().height))
758       {
759         g.drawImage(image, 0, 0, this);
760         fastPaint = false;
761         return;
762       }
763     }
764     imgWidth = (av.endRes - av.startRes + 1) * av.charWidth;
765     if (imgWidth < 1)
766       return;
767     if (image == null || imgWidth != image.getWidth(this)
768             || image.getHeight(this) != getHeight())
769     {
770       try
771       {
772         image = new BufferedImage(imgWidth, ap.annotationPanel.getHeight(),
773                 BufferedImage.TYPE_INT_RGB);
774       } catch (OutOfMemoryError oom)
775       {
776         try
777         {
778           System.gc();
779         } catch (Exception x)
780         {
781         }
782         ;
783         new OOMWarning(
784                 "Couldn't allocate memory to redraw screen. Please restart Jalview",
785                 oom);
786         return;
787       }
788       gg = (Graphics2D) image.getGraphics();
789
790       if (av.antiAlias)
791       {
792         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
793                 RenderingHints.VALUE_ANTIALIAS_ON);
794       }
795
796       gg.setFont(av.getFont());
797       fm = gg.getFontMetrics();
798       gg.setColor(Color.white);
799       gg.fillRect(0, 0, imgWidth, image.getHeight());
800       imageFresh = true;
801     }
802
803     drawComponent(gg, av.startRes, av.endRes + 1);
804     imageFresh = false;
805     g.drawImage(image, 0, 0, this);
806   }
807   /**
808    * set true to enable redraw timing debug output on stderr
809    */
810   private final boolean debugRedraw = false;
811   /**
812    * non-Thread safe repaint
813    * 
814    * @param horizontal
815    *          repaint with horizontal shift in alignment
816    */
817   public void fastPaint(int horizontal)
818   {
819     if ((horizontal == 0) || gg == null
820             || av.getAlignment().getAlignmentAnnotation() == null
821             || av.getAlignment().getAlignmentAnnotation().length < 1
822             || av.isCalcInProgress())
823     {
824       repaint();
825       return;
826     }
827     long stime=System.currentTimeMillis();
828     gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.charWidth, 0);
829     long mtime=System.currentTimeMillis();
830     int sr = av.startRes;
831     int er = av.endRes + 1;
832     int transX = 0;
833
834     if (horizontal > 0) // scrollbar pulled right, image to the left
835     {
836       transX = (er - sr - horizontal) * av.charWidth;
837       sr = er - horizontal;
838     }
839     else if (horizontal < 0)
840     {
841       er = sr - horizontal;
842     }
843
844     gg.translate(transX, 0);
845
846     drawComponent(gg, sr, er);
847
848     gg.translate(-transX, 0);
849     long dtime=System.currentTimeMillis();
850     fastPaint = true;
851     repaint();
852     long rtime=System.currentTimeMillis();
853     if (debugRedraw) {
854       System.err.println("Scroll:\t"+horizontal+"\tCopyArea:\t"+(mtime-stime)+"\tDraw component:\t"+(dtime-mtime)+"\tRepaint call:\t"+(rtime-dtime));
855     }
856
857   }
858
859   private volatile boolean lastImageGood = false;
860
861   /**
862    * DOCUMENT ME!
863    * 
864    * @param g
865    *          DOCUMENT ME!
866    * @param startRes
867    *          DOCUMENT ME!
868    * @param endRes
869    *          DOCUMENT ME!
870    */
871   public void drawComponent(Graphics g, int startRes, int endRes)
872   {
873     BufferedImage oldFaded = fadedImage;
874     if (av.isCalcInProgress())
875     {
876       if (image == null)
877       {
878         lastImageGood = false;
879         return;
880       }
881       // We'll keep a record of the old image,
882       // and draw a faded image until the calculation
883       // has completed
884       if (lastImageGood
885               && (fadedImage == null || fadedImage.getWidth() != imgWidth || fadedImage
886                       .getHeight() != image.getHeight()))
887       {
888         // System.err.println("redraw faded image ("+(fadedImage==null ?
889         // "null image" : "") + " lastGood="+lastImageGood+")");
890         fadedImage = new BufferedImage(imgWidth, image.getHeight(),
891                 BufferedImage.TYPE_INT_RGB);
892
893         Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics();
894
895         fadedG.setColor(Color.white);
896         fadedG.fillRect(0, 0, imgWidth, image.getHeight());
897
898         fadedG.setComposite(AlphaComposite.getInstance(
899                 AlphaComposite.SRC_OVER, .3f));
900         fadedG.drawImage(image, 0, 0, this);
901
902       }
903       // make sure we don't overwrite the last good faded image until all
904       // calculations have finished
905       lastImageGood = false;
906
907     }
908     else
909     {
910       if (fadedImage != null)
911       {
912         oldFaded = fadedImage;
913       }
914       fadedImage = null;
915     }
916     
917     g.setColor(Color.white);
918     g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getHeight());
919
920     g.setFont(av.getFont());
921     if (fm == null)
922     {
923       fm = g.getFontMetrics();
924     }
925
926     if ((av.getAlignment().getAlignmentAnnotation() == null)
927             || (av.getAlignment().getAlignmentAnnotation().length < 1))
928     {
929       g.setColor(Color.white);
930       g.fillRect(0, 0, getWidth(), getHeight());
931       g.setColor(Color.black);
932       if (av.validCharWidth)
933       {
934         g.drawString(MessageManager.getString("label.alignment_has_no_annotations"), 20, 15);
935       }
936
937       return;
938     }
939     lastImageGood = renderer.drawComponent(this, av, g, activeRow,
940             startRes, endRes);
941     if (!lastImageGood && fadedImage == null)
942     {
943       fadedImage = oldFaded;
944     }
945   }
946
947   @Override
948   public FontMetrics getFontMetrics()
949   {
950     return fm;
951   }
952
953   @Override
954   public Image getFadedImage()
955   {
956     return fadedImage;
957   }
958
959   @Override
960   public int getFadedImageWidth()
961   {
962     return imgWidth;
963   }
964   private int[] bounds = new int[2];
965   @Override
966   public int[] getVisibleVRange()
967   {
968     if (ap!=null && ap.alabels!=null)
969     {
970     int sOffset=-ap.alabels.scrollOffset;
971     int visHeight = sOffset+ap.annotationSpaceFillerHolder.getHeight();
972     bounds[0] = sOffset; bounds[1]=visHeight;
973     return bounds;
974     } else return null;
975   }
976 }