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